From 757c264bc10ebc71074ee3f5fb66d670667a09bc Mon Sep 17 00:00:00 2001 From: Kevin Cheng Date: Thu, 18 Apr 2019 11:31:16 -0700 Subject: Add in cffi 1.12.2 (e0c7666) Since this is a mercurial repo, d/led zip of src: https://bitbucket.org/cffi/cffi/get/v1.12.2.zip Also add in misc METADATA/NOTICE/Android.bp/etc files. Bug: 122778810 Test: None Change-Id: I36c58ed07a2cdd4d9d11831908175a5c988f33c1 --- AUTHORS | 8 + LICENSE | 26 + MANIFEST.in | 6 + METADATA | 17 + MODULE_LICENSE_MIT | 0 NOTICE | 1 + README.md | 30 + c/.libs_cffi_backend/libffi-45372312.so.6.0.4 | Bin 0 -> 149064 bytes c/Android.bp | 53 + c/_cffi_backend.c | 7686 ++++++++++++++++++++ c/_cffi_backend.so | Bin 0 -> 787992 bytes c/_dummy_file_cffi_backend.py | 0 c/_dummy_file_libffi.py | 0 c/call_python.c | 278 + c/cdlopen.c | 361 + c/cffi1_module.c | 231 + c/cglob.c | 113 + c/commontypes.c | 216 + c/ffi_obj.c | 1221 ++++ c/file_emulator.h | 93 + c/lib_obj.c | 712 ++ c/libffi_msvc/LICENSE | 20 + c/libffi_msvc/README | 502 ++ c/libffi_msvc/README.ctypes | 7 + c/libffi_msvc/ffi.c | 486 ++ c/libffi_msvc/ffi.h | 322 + c/libffi_msvc/ffi_common.h | 77 + c/libffi_msvc/fficonfig.h | 96 + c/libffi_msvc/ffitarget.h | 85 + c/libffi_msvc/prep_cif.c | 181 + c/libffi_msvc/types.c | 104 + c/libffi_msvc/win32.c | 162 + c/libffi_msvc/win64.asm | 156 + c/libffi_msvc/win64.obj | Bin 0 -> 1176 bytes c/malloc_closure.h | 176 + c/minibuffer.h | 408 ++ c/misc_thread_common.h | 371 + c/misc_thread_posix.h | 49 + c/misc_win32.h | 241 + c/parse_c_type.c | 847 +++ c/realize_c_type.c | 797 ++ c/test_c.py | 4256 +++++++++++ c/wchar_helper.h | 246 + c/wchar_helper_3.h | 149 + cffi/Android.bp | 44 + cffi/__init__.py | 14 + cffi/_cffi_errors.h | 147 + cffi/_cffi_include.h | 308 + cffi/_embedding.h | 484 ++ cffi/api.py | 961 +++ cffi/backend_ctypes.py | 1121 +++ cffi/cffi_opcode.py | 187 + cffi/commontypes.py | 80 + cffi/cparser.py | 923 +++ cffi/error.py | 31 + cffi/ffiplatform.py | 127 + cffi/lock.py | 30 + cffi/model.py | 614 ++ cffi/parse_c_type.h | 181 + cffi/pkgconfig.py | 121 + cffi/recompiler.py | 1542 ++++ cffi/setuptools_ext.py | 217 + cffi/vengine_cpy.py | 1015 +++ cffi/vengine_gen.py | 675 ++ cffi/verifier.py | 306 + demo/_curses.py | 1075 +++ demo/_curses_build.py | 327 + demo/_curses_setup.py | 13 + demo/api.py | 62 + demo/bsdopendirtype.py | 48 + demo/bsdopendirtype_build.py | 23 + demo/bsdopendirtype_setup.py | 13 + demo/btrfs-snap.py | 52 + demo/cffi-cocoa.py | 102 + demo/embedding.py | 21 + demo/embedding_test.c | 43 + demo/extern_python.py | 26 + demo/extern_python_varargs.py | 61 + demo/fastcsv.py | 266 + demo/gmp.py | 33 + demo/gmp_build.py | 26 + demo/manual.c | 166 + demo/manual2.py | 34 + demo/pwuid.py | 7 + demo/pwuid_build.py | 18 + demo/py.cleanup | 31 + demo/pyobj.py | 124 + demo/readdir.py | 35 + demo/readdir2.py | 35 + demo/readdir2_build.py | 36 + demo/readdir2_setup.py | 9 + demo/readdir_build.py | 33 + demo/readdir_ctypes.py | 69 + demo/readdir_setup.py | 11 + demo/recopendirtype.py | 50 + demo/recopendirtype_build.py | 19 + demo/setup_manual.py | 5 + demo/winclipboard.py | 40 + demo/winclipboard_build.py | 36 + demo/xclient.py | 27 + demo/xclient_build.py | 25 + doc/Makefile | 89 + doc/make.bat | 113 + doc/misc/design.rst | 51 + doc/misc/grant-cffi-1.0.rst | 124 + doc/misc/parse_c_type.rst | 72 + doc/source/cdef.rst | 992 +++ doc/source/conf.py | 194 + doc/source/embedding.rst | 524 ++ doc/source/goals.rst | 69 + doc/source/index.rst | 22 + doc/source/installation.rst | 206 + doc/source/overview.rst | 651 ++ doc/source/ref.rst | 1007 +++ doc/source/using.rst | 1027 +++ doc/source/whatsnew.rst | 714 ++ requirements.txt | 2 + setup.cfg | 2 + setup.py | 250 + setup_base.py | 22 + testing/__init__.py | 0 testing/cffi0/__init__.py | 0 testing/cffi0/backend_tests.py | 1990 +++++ testing/cffi0/callback_in_thread.py | 42 + testing/cffi0/snippets/distutils_module/setup.py | 7 + .../snippets/distutils_module/snip_basic_verify.py | 17 + .../cffi0/snippets/distutils_package_1/setup.py | 7 + .../snip_basic_verify1/__init__.py | 17 + .../cffi0/snippets/distutils_package_2/setup.py | 8 + .../snip_basic_verify2/__init__.py | 18 + testing/cffi0/snippets/infrastructure/setup.py | 5 + .../infrastructure/snip_infrastructure/__init__.py | 3 + testing/cffi0/snippets/setuptools_module/setup.py | 8 + .../setuptools_module/snip_setuptools_verify.py | 17 + .../cffi0/snippets/setuptools_package_1/setup.py | 8 + .../snip_setuptools_verify1/__init__.py | 17 + .../cffi0/snippets/setuptools_package_2/setup.py | 9 + .../snip_setuptools_verify2/__init__.py | 18 + testing/cffi0/test_cdata.py | 41 + testing/cffi0/test_ctypes.py | 43 + testing/cffi0/test_ffi_backend.py | 576 ++ testing/cffi0/test_function.py | 520 ++ testing/cffi0/test_model.py | 111 + testing/cffi0/test_ownlib.py | 373 + testing/cffi0/test_parsing.py | 468 ++ testing/cffi0/test_platform.py | 25 + testing/cffi0/test_unicode_literals.py | 79 + testing/cffi0/test_verify.py | 2524 +++++++ testing/cffi0/test_verify2.py | 9 + testing/cffi0/test_version.py | 62 + testing/cffi0/test_vgen.py | 12 + testing/cffi0/test_vgen2.py | 13 + testing/cffi0/test_zdistutils.py | 288 + testing/cffi0/test_zintegration.py | 176 + testing/cffi1/__init__.py | 0 testing/cffi1/test_cffi_binary.py | 20 + testing/cffi1/test_commontypes.py | 34 + testing/cffi1/test_dlopen.py | 225 + testing/cffi1/test_dlopen_unicode_literals.py | 9 + testing/cffi1/test_ffi_obj.py | 532 ++ testing/cffi1/test_new_ffi_1.py | 1793 +++++ testing/cffi1/test_parse_c_type.py | 372 + testing/cffi1/test_pkgconfig.py | 94 + testing/cffi1/test_re_python.py | 256 + testing/cffi1/test_realize_c_type.py | 73 + testing/cffi1/test_recompiler.py | 2316 ++++++ testing/cffi1/test_unicode_literals.py | 43 + testing/cffi1/test_verify1.py | 2363 ++++++ testing/cffi1/test_zdist.py | 426 ++ testing/embedding/__init__.py | 0 testing/embedding/add1-test.c | 21 + testing/embedding/add1.py | 33 + testing/embedding/add2-test.c | 14 + testing/embedding/add2.py | 29 + testing/embedding/add3.py | 24 + testing/embedding/add_recursive-test.c | 27 + testing/embedding/add_recursive.py | 33 + testing/embedding/empty.py | 10 + testing/embedding/initerror.py | 18 + testing/embedding/perf-test.c | 90 + testing/embedding/perf.py | 21 + testing/embedding/test_basic.py | 207 + testing/embedding/test_performance.py | 52 + testing/embedding/test_recursive.py | 15 + testing/embedding/test_thread.py | 61 + testing/embedding/test_tlocal.py | 10 + testing/embedding/thread-test.h | 96 + testing/embedding/thread1-test.c | 43 + testing/embedding/thread2-test.c | 57 + testing/embedding/thread3-test.c | 56 + testing/embedding/tlocal-test.c | 47 + testing/embedding/tlocal.py | 33 + testing/support.py | 88 + testing/udir.py | 13 + 194 files changed, 56215 insertions(+) create mode 100644 AUTHORS create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 METADATA create mode 100644 MODULE_LICENSE_MIT create mode 120000 NOTICE create mode 100644 README.md create mode 100644 c/.libs_cffi_backend/libffi-45372312.so.6.0.4 create mode 100644 c/Android.bp create mode 100644 c/_cffi_backend.c create mode 100644 c/_cffi_backend.so create mode 100644 c/_dummy_file_cffi_backend.py create mode 100644 c/_dummy_file_libffi.py create mode 100644 c/call_python.c create mode 100644 c/cdlopen.c create mode 100644 c/cffi1_module.c create mode 100644 c/cglob.c create mode 100644 c/commontypes.c create mode 100644 c/ffi_obj.c create mode 100644 c/file_emulator.h create mode 100644 c/lib_obj.c create mode 100644 c/libffi_msvc/LICENSE create mode 100644 c/libffi_msvc/README create mode 100644 c/libffi_msvc/README.ctypes create mode 100644 c/libffi_msvc/ffi.c create mode 100644 c/libffi_msvc/ffi.h create mode 100644 c/libffi_msvc/ffi_common.h create mode 100644 c/libffi_msvc/fficonfig.h create mode 100644 c/libffi_msvc/ffitarget.h create mode 100644 c/libffi_msvc/prep_cif.c create mode 100644 c/libffi_msvc/types.c create mode 100644 c/libffi_msvc/win32.c create mode 100644 c/libffi_msvc/win64.asm create mode 100644 c/libffi_msvc/win64.obj create mode 100644 c/malloc_closure.h create mode 100644 c/minibuffer.h create mode 100644 c/misc_thread_common.h create mode 100644 c/misc_thread_posix.h create mode 100644 c/misc_win32.h create mode 100644 c/parse_c_type.c create mode 100644 c/realize_c_type.c create mode 100644 c/test_c.py create mode 100644 c/wchar_helper.h create mode 100644 c/wchar_helper_3.h create mode 100644 cffi/Android.bp create mode 100644 cffi/__init__.py create mode 100644 cffi/_cffi_errors.h create mode 100644 cffi/_cffi_include.h create mode 100644 cffi/_embedding.h create mode 100644 cffi/api.py create mode 100644 cffi/backend_ctypes.py create mode 100644 cffi/cffi_opcode.py create mode 100644 cffi/commontypes.py create mode 100644 cffi/cparser.py create mode 100644 cffi/error.py create mode 100644 cffi/ffiplatform.py create mode 100644 cffi/lock.py create mode 100644 cffi/model.py create mode 100644 cffi/parse_c_type.h create mode 100644 cffi/pkgconfig.py create mode 100644 cffi/recompiler.py create mode 100644 cffi/setuptools_ext.py create mode 100644 cffi/vengine_cpy.py create mode 100644 cffi/vengine_gen.py create mode 100644 cffi/verifier.py create mode 100644 demo/_curses.py create mode 100644 demo/_curses_build.py create mode 100644 demo/_curses_setup.py create mode 100644 demo/api.py create mode 100644 demo/bsdopendirtype.py create mode 100644 demo/bsdopendirtype_build.py create mode 100644 demo/bsdopendirtype_setup.py create mode 100644 demo/btrfs-snap.py create mode 100644 demo/cffi-cocoa.py create mode 100644 demo/embedding.py create mode 100644 demo/embedding_test.c create mode 100644 demo/extern_python.py create mode 100644 demo/extern_python_varargs.py create mode 100644 demo/fastcsv.py create mode 100644 demo/gmp.py create mode 100644 demo/gmp_build.py create mode 100644 demo/manual.c create mode 100644 demo/manual2.py create mode 100644 demo/pwuid.py create mode 100644 demo/pwuid_build.py create mode 100755 demo/py.cleanup create mode 100644 demo/pyobj.py create mode 100644 demo/readdir.py create mode 100644 demo/readdir2.py create mode 100644 demo/readdir2_build.py create mode 100644 demo/readdir2_setup.py create mode 100644 demo/readdir_build.py create mode 100644 demo/readdir_ctypes.py create mode 100644 demo/readdir_setup.py create mode 100644 demo/recopendirtype.py create mode 100644 demo/recopendirtype_build.py create mode 100644 demo/setup_manual.py create mode 100644 demo/winclipboard.py create mode 100644 demo/winclipboard_build.py create mode 100644 demo/xclient.py create mode 100644 demo/xclient_build.py create mode 100644 doc/Makefile create mode 100644 doc/make.bat create mode 100644 doc/misc/design.rst create mode 100644 doc/misc/grant-cffi-1.0.rst create mode 100644 doc/misc/parse_c_type.rst create mode 100644 doc/source/cdef.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/embedding.rst create mode 100644 doc/source/goals.rst create mode 100644 doc/source/index.rst create mode 100644 doc/source/installation.rst create mode 100644 doc/source/overview.rst create mode 100644 doc/source/ref.rst create mode 100644 doc/source/using.rst create mode 100644 doc/source/whatsnew.rst create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 setup_base.py create mode 100644 testing/__init__.py create mode 100644 testing/cffi0/__init__.py create mode 100644 testing/cffi0/backend_tests.py create mode 100644 testing/cffi0/callback_in_thread.py create mode 100644 testing/cffi0/snippets/distutils_module/setup.py create mode 100644 testing/cffi0/snippets/distutils_module/snip_basic_verify.py create mode 100644 testing/cffi0/snippets/distutils_package_1/setup.py create mode 100644 testing/cffi0/snippets/distutils_package_1/snip_basic_verify1/__init__.py create mode 100644 testing/cffi0/snippets/distutils_package_2/setup.py create mode 100644 testing/cffi0/snippets/distutils_package_2/snip_basic_verify2/__init__.py create mode 100644 testing/cffi0/snippets/infrastructure/setup.py create mode 100644 testing/cffi0/snippets/infrastructure/snip_infrastructure/__init__.py create mode 100644 testing/cffi0/snippets/setuptools_module/setup.py create mode 100644 testing/cffi0/snippets/setuptools_module/snip_setuptools_verify.py create mode 100644 testing/cffi0/snippets/setuptools_package_1/setup.py create mode 100644 testing/cffi0/snippets/setuptools_package_1/snip_setuptools_verify1/__init__.py create mode 100644 testing/cffi0/snippets/setuptools_package_2/setup.py create mode 100644 testing/cffi0/snippets/setuptools_package_2/snip_setuptools_verify2/__init__.py create mode 100644 testing/cffi0/test_cdata.py create mode 100644 testing/cffi0/test_ctypes.py create mode 100644 testing/cffi0/test_ffi_backend.py create mode 100644 testing/cffi0/test_function.py create mode 100644 testing/cffi0/test_model.py create mode 100644 testing/cffi0/test_ownlib.py create mode 100644 testing/cffi0/test_parsing.py create mode 100644 testing/cffi0/test_platform.py create mode 100644 testing/cffi0/test_unicode_literals.py create mode 100644 testing/cffi0/test_verify.py create mode 100644 testing/cffi0/test_verify2.py create mode 100644 testing/cffi0/test_version.py create mode 100644 testing/cffi0/test_vgen.py create mode 100644 testing/cffi0/test_vgen2.py create mode 100644 testing/cffi0/test_zdistutils.py create mode 100644 testing/cffi0/test_zintegration.py create mode 100644 testing/cffi1/__init__.py create mode 100644 testing/cffi1/test_cffi_binary.py create mode 100644 testing/cffi1/test_commontypes.py create mode 100644 testing/cffi1/test_dlopen.py create mode 100644 testing/cffi1/test_dlopen_unicode_literals.py create mode 100644 testing/cffi1/test_ffi_obj.py create mode 100644 testing/cffi1/test_new_ffi_1.py create mode 100644 testing/cffi1/test_parse_c_type.py create mode 100644 testing/cffi1/test_pkgconfig.py create mode 100644 testing/cffi1/test_re_python.py create mode 100644 testing/cffi1/test_realize_c_type.py create mode 100644 testing/cffi1/test_recompiler.py create mode 100644 testing/cffi1/test_unicode_literals.py create mode 100644 testing/cffi1/test_verify1.py create mode 100644 testing/cffi1/test_zdist.py create mode 100644 testing/embedding/__init__.py create mode 100644 testing/embedding/add1-test.c create mode 100644 testing/embedding/add1.py create mode 100644 testing/embedding/add2-test.c create mode 100644 testing/embedding/add2.py create mode 100644 testing/embedding/add3.py create mode 100644 testing/embedding/add_recursive-test.c create mode 100644 testing/embedding/add_recursive.py create mode 100644 testing/embedding/empty.py create mode 100644 testing/embedding/initerror.py create mode 100644 testing/embedding/perf-test.c create mode 100644 testing/embedding/perf.py create mode 100644 testing/embedding/test_basic.py create mode 100644 testing/embedding/test_performance.py create mode 100644 testing/embedding/test_recursive.py create mode 100644 testing/embedding/test_thread.py create mode 100644 testing/embedding/test_tlocal.py create mode 100644 testing/embedding/thread-test.h create mode 100644 testing/embedding/thread1-test.c create mode 100644 testing/embedding/thread2-test.c create mode 100644 testing/embedding/thread3-test.c create mode 100644 testing/embedding/tlocal-test.c create mode 100644 testing/embedding/tlocal.py create mode 100644 testing/support.py create mode 100644 testing/udir.py diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..370a25d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,8 @@ +This package has been mostly done by Armin Rigo with help from +Maciej FijaƂkowski. The idea is heavily based (although not directly +copied) from LuaJIT ffi by Mike Pall. + + +Other contributors: + + Google Inc. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29225ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ + +Except when otherwise stated (look for LICENSE files in directories or +information at the beginning of each file) all software and +documentation is licensed as follows: + + The MIT License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..e9fdd6a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +recursive-include cffi *.py *.h +recursive-include c *.c *.h *.asm *.py win64.obj +recursive-include testing *.py *.c *.h +recursive-include doc *.py *.rst Makefile *.bat +recursive-include demo py.cleanup *.py embedding_test.c manual.c +include AUTHORS LICENSE setup.py setup_base.py diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..512e1f3 --- /dev/null +++ b/METADATA @@ -0,0 +1,17 @@ +name: "cffi" +description: + "Foreign Function Interface for Python calling C code." + +third_party { + url { + type: HOMEPAGE + value: "https://bitbucket.org/cffi/cffi" + } + url { + type: HG + value: "https://bitbucket.org/cffi/cffi/src" + } + version: "1.12.2" + last_upgrade_date { year: 2019 month: 2 day: 26 } + license_type: NOTICE +} diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 diff --git a/NOTICE b/NOTICE new file mode 120000 index 0000000..7a694c9 --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +LICENSE \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e7862d --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +CFFI +==== + +Foreign Function Interface for Python calling C code. +Please see the [Documentation](http://cffi.readthedocs.org/) or uncompiled +in the doc/ subdirectory. + +Download +-------- + +[Download page](https://bitbucket.org/cffi/cffi/downloads) + +Contact +------- + +[Mailing list](https://groups.google.com/forum/#!forum/python-cffi) + +Testing/development tips +------------------------ + +To run tests under CPython, run:: + + pip install pytest # if you don't have py.test already + pip install pycparser + python setup.py build_ext -f -i + py.test c/ testing/ + +If you run in another directory (either the tests or another program), +you should use the environment variable ``PYTHONPATH=/path`` to point +to the location that contains the ``_cffi_backend.so`` just compiled. diff --git a/c/.libs_cffi_backend/libffi-45372312.so.6.0.4 b/c/.libs_cffi_backend/libffi-45372312.so.6.0.4 new file mode 100644 index 0000000..59e65c0 Binary files /dev/null and b/c/.libs_cffi_backend/libffi-45372312.so.6.0.4 differ diff --git a/c/Android.bp b/c/Android.bp new file mode 100644 index 0000000..baced02 --- /dev/null +++ b/c/Android.bp @@ -0,0 +1,53 @@ +python_library { + name: "py-cffi-backend", + host_supported: true, + srcs: [ + "_dummy_file_cffi_backend.py", + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: true, + }, + }, + data: [ + ":py-cffi-backend-files" + ], +} + +filegroup { + name: "py-cffi-backend-files", + srcs: [ + "_cffi_backend.so", + ], +} + +python_library { + name: "py-cffi-backend-libffi", + host_supported: true, + srcs: [ + "_dummy_file_libffi.py", + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: true, + }, + }, + data: [ + ":py-cffi-backend-libffi-files" + ], +} + +filegroup { + name: "py-cffi-backend-libffi-files", + srcs: [ + ".libs_cffi_backend/libffi-45372312.so.6.0.4", + ], +} + + diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c new file mode 100644 index 0000000..c39866a --- /dev/null +++ b/c/_cffi_backend.c @@ -0,0 +1,7686 @@ +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" + +#define CFFI_VERSION "1.12.2" + +#ifdef MS_WIN32 +#include +#include "misc_win32.h" +#else +#include +#include +#include +#include +#include +#include +#endif + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ + typedef unsigned char _Bool; +# endif +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +#endif + + +/* Define the following macro ONLY if you trust libffi's version of + * ffi_closure_alloc() more than the code in malloc_closure.h. + * IMPORTANT: DO NOT ENABLE THIS ON LINUX, unless you understand exactly + * why I recommend against it and decide that you trust it more than my + * analysis below. + * + * There are two versions of this code: one inside libffi itself, and + * one inside malloc_closure.h here. Both should be fine as long as the + * Linux distribution does _not_ enable extra security features. If it + * does, then the code in malloc_closure.h will cleanly crash because + * there is no reasonable way to obtain a read-write-execute memory + * page. On the other hand, the code in libffi will appear to + * work---but will actually randomly crash after a fork() if the child + * does not immediately call exec(). This second crash is of the kind + * that can be turned into an attack vector by a motivated attacker. + * So, _enabling_ extra security features _opens_ an attack vector. + * That sounds like a horribly bad idea to me, and is the reason for why + * I prefer CFFI crashing cleanly. + * + * Currently, we use libffi's ffi_closure_alloc() only on NetBSD. It is + * known that on the NetBSD kernel, a different strategy is used which + * should not be open to the fork() bug. + */ +#ifdef __NetBSD__ +# define CFFI_TRUST_LIBFFI +#endif + +#ifndef CFFI_TRUST_LIBFFI +# include "malloc_closure.h" +#endif + + +#if PY_MAJOR_VERSION >= 3 +# define STR_OR_BYTES "bytes" +# define PyText_Type PyUnicode_Type +# define PyText_Check PyUnicode_Check +# define PyTextAny_Check PyUnicode_Check +# define PyText_FromFormat PyUnicode_FromFormat +# define PyText_AsUTF8 _PyUnicode_AsString /* PyUnicode_AsUTF8 in Py3.3 */ +# define PyText_AS_UTF8 _PyUnicode_AsString +# if PY_VERSION_HEX >= 0x03030000 +# define PyText_GetSize PyUnicode_GetLength +# else +# define PyText_GetSize PyUnicode_GetSize +# endif +# define PyText_FromString PyUnicode_FromString +# define PyText_FromStringAndSize PyUnicode_FromStringAndSize +# define PyText_InternInPlace PyUnicode_InternInPlace +# define PyText_InternFromString PyUnicode_InternFromString +# define PyIntOrLong_Check PyLong_Check +#else +# define STR_OR_BYTES "str" +# define PyText_Type PyString_Type +# define PyText_Check PyString_Check +# define PyTextAny_Check(op) (PyString_Check(op) || PyUnicode_Check(op)) +# define PyText_FromFormat PyString_FromFormat +# define PyText_AsUTF8 PyString_AsString +# define PyText_AS_UTF8 PyString_AS_STRING +# define PyText_GetSize PyString_Size +# define PyText_FromString PyString_FromString +# define PyText_FromStringAndSize PyString_FromStringAndSize +# define PyText_InternInPlace PyString_InternInPlace +# define PyText_InternFromString PyString_InternFromString +# define PyIntOrLong_Check(op) (PyInt_Check(op) || PyLong_Check(op)) +#endif + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +# define PyInt_FromSsize_t PyLong_FromSsize_t +# define PyInt_AsSsize_t PyLong_AsSsize_t +# define PyInt_AsLong PyLong_AsLong +#endif + +#if PY_MAJOR_VERSION >= 3 +/* This is the default on Python3 and constant has been removed. */ +# define Py_TPFLAGS_CHECKTYPES 0 +#endif + +#if PY_MAJOR_VERSION < 3 +# undef PyCapsule_GetPointer +# undef PyCapsule_New +# define PyCapsule_GetPointer(capsule, name) \ + (PyCObject_AsVoidPtr(capsule)) +# define PyCapsule_New(pointer, name, destructor) \ + (PyCObject_FromVoidPtr(pointer, destructor)) +#endif + +/************************************************************/ + +/* base type flag: exactly one of the following: */ +#define CT_PRIMITIVE_SIGNED 0x001 /* signed integer */ +#define CT_PRIMITIVE_UNSIGNED 0x002 /* unsigned integer */ +#define CT_PRIMITIVE_CHAR 0x004 /* char, wchar_t, charN_t */ +#define CT_PRIMITIVE_FLOAT 0x008 /* float, double, long double */ +#define CT_POINTER 0x010 /* pointer, excluding ptr-to-func */ +#define CT_ARRAY 0x020 /* array */ +#define CT_STRUCT 0x040 /* struct */ +#define CT_UNION 0x080 /* union */ +#define CT_FUNCTIONPTR 0x100 /* pointer to function */ +#define CT_VOID 0x200 /* void */ +#define CT_PRIMITIVE_COMPLEX 0x400 /* float _Complex, double _Complex */ + +/* other flags that may also be set in addition to the base flag: */ +#define CT_IS_VOIDCHAR_PTR 0x00001000 +#define CT_PRIMITIVE_FITS_LONG 0x00002000 +#define CT_IS_OPAQUE 0x00004000 +#define CT_IS_ENUM 0x00008000 +#define CT_IS_PTR_TO_OWNED 0x00010000 /* only owned if CDataOwning_Type */ +#define CT_CUSTOM_FIELD_POS 0x00020000 +#define CT_IS_LONGDOUBLE 0x00040000 +#define CT_IS_BOOL 0x00080000 +#define CT_IS_FILE 0x00100000 +#define CT_IS_VOID_PTR 0x00200000 +#define CT_WITH_VAR_ARRAY 0x00400000 +/* unused 0x00800000 */ +#define CT_LAZY_FIELD_LIST 0x01000000 +#define CT_WITH_PACKED_CHANGE 0x02000000 +#define CT_IS_SIGNED_WCHAR 0x04000000 +#define CT_PRIMITIVE_ANY (CT_PRIMITIVE_SIGNED | \ + CT_PRIMITIVE_UNSIGNED | \ + CT_PRIMITIVE_CHAR | \ + CT_PRIMITIVE_FLOAT | \ + CT_PRIMITIVE_COMPLEX) + +typedef struct _ctypedescr { + PyObject_VAR_HEAD + + struct _ctypedescr *ct_itemdescr; /* ptrs and arrays: the item type */ + PyObject *ct_stuff; /* structs: dict of the fields + arrays: ctypedescr of the ptr type + function: tuple(abi, ctres, ctargs..) + enum: pair {"name":x},{x:"name"} + ptrs: lazily, ctypedescr of array */ + void *ct_extra; /* structs: first field (not a ref!) + function types: cif_description + primitives: prebuilt "cif" object */ + + PyObject *ct_weakreflist; /* weakref support */ + + PyObject *ct_unique_key; /* key in unique_cache (a string, but not + human-readable) */ + + Py_ssize_t ct_size; /* size of instances, or -1 if unknown */ + Py_ssize_t ct_length; /* length of arrays, or -1 if unknown; + or alignment of primitive and struct types; + always -1 for pointers */ + int ct_flags; /* CT_xxx flags */ + + int ct_name_position; /* index in ct_name of where to put a var name */ + char ct_name[1]; /* string, e.g. "int *" for pointers to ints */ +} CTypeDescrObject; + +typedef struct { + PyObject_HEAD + CTypeDescrObject *c_type; + char *c_data; + PyObject *c_weakreflist; +} CDataObject; + +typedef struct cfieldobject_s { + PyObject_HEAD + CTypeDescrObject *cf_type; + Py_ssize_t cf_offset; + short cf_bitshift; /* >= 0: bitshift; or BS_REGULAR or BS_EMPTY_ARRAY */ + short cf_bitsize; + unsigned char cf_flags; /* BF_... */ + struct cfieldobject_s *cf_next; +} CFieldObject; +#define BS_REGULAR (-1) /* a regular field, not with bitshift */ +#define BS_EMPTY_ARRAY (-2) /* a field declared 'type[0]' or 'type[]' */ +#define BF_IGNORE_IN_CTOR 0x01 /* union field not in the first place */ + +static PyTypeObject CTypeDescr_Type; +static PyTypeObject CField_Type; +static PyTypeObject CData_Type; +static PyTypeObject CDataOwning_Type; +static PyTypeObject CDataOwningGC_Type; +static PyTypeObject CDataGCP_Type; + +#define CTypeDescr_Check(ob) (Py_TYPE(ob) == &CTypeDescr_Type) +#define CData_Check(ob) (Py_TYPE(ob) == &CData_Type || \ + Py_TYPE(ob) == &CDataOwning_Type || \ + Py_TYPE(ob) == &CDataOwningGC_Type || \ + Py_TYPE(ob) == &CDataGCP_Type) +#define CDataOwn_Check(ob) (Py_TYPE(ob) == &CDataOwning_Type || \ + Py_TYPE(ob) == &CDataOwningGC_Type) + +typedef union { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +} union_alignment; + +typedef struct { + CDataObject head; + union_alignment alignment; +} CDataObject_casted_primitive; + +typedef struct { + CDataObject head; + union_alignment alignment; +} CDataObject_own_nolength; + +typedef struct { + CDataObject head; + Py_ssize_t length; + union_alignment alignment; +} CDataObject_own_length; + +typedef struct { + CDataObject head; + PyObject *structobj; +} CDataObject_own_structptr; + +typedef struct { + CDataObject head; + Py_ssize_t length; /* same as CDataObject_own_length up to here */ + Py_buffer *bufferview; +} CDataObject_owngc_frombuf; + +typedef struct { + CDataObject head; + Py_ssize_t length; /* same as CDataObject_own_length up to here */ + PyObject *origobj; + PyObject *destructor; +} CDataObject_gcp; + +typedef struct { + CDataObject head; + ffi_closure *closure; +} CDataObject_closure; + +typedef struct { + ffi_cif cif; + /* the following information is used when doing the call: + - a buffer of size 'exchange_size' is malloced + - the arguments are converted from Python objects to raw data + - the i'th raw data is stored at 'buffer + exchange_offset_arg[1+i]' + - the call is done + - the result is read back from 'buffer + exchange_offset_arg[0]' */ + Py_ssize_t exchange_size; + Py_ssize_t exchange_offset_arg[1]; +} cif_description_t; + +#define ADD_WRAPAROUND(x, y) ((Py_ssize_t)(((size_t)(x)) + ((size_t)(y)))) +#define MUL_WRAPAROUND(x, y) ((Py_ssize_t)(((size_t)(x)) * ((size_t)(y)))) + + +/* whenever running Python code, the errno is saved in this thread-local + variable */ +#ifndef MS_WIN32 +# include "misc_thread_posix.h" +#endif + +#include "minibuffer.h" + +#if PY_MAJOR_VERSION >= 3 +# include "file_emulator.h" +#endif + +#ifdef PyUnicode_KIND /* Python >= 3.3 */ +# include "wchar_helper_3.h" +#else +# include "wchar_helper.h" +#endif + +#include "../cffi/_cffi_errors.h" + +typedef struct _cffi_allocator_s { + PyObject *ca_alloc, *ca_free; + int ca_dont_clear; +} cffi_allocator_t; +static const cffi_allocator_t default_allocator = { NULL, NULL, 0 }; +static PyObject *FFIError; +static PyObject *unique_cache; + +/************************************************************/ + +static CTypeDescrObject * +ctypedescr_new(int name_size) +{ + CTypeDescrObject *ct = PyObject_GC_NewVar(CTypeDescrObject, + &CTypeDescr_Type, + name_size); + if (ct == NULL) + return NULL; + + ct->ct_itemdescr = NULL; + ct->ct_stuff = NULL; + ct->ct_weakreflist = NULL; + ct->ct_unique_key = NULL; + PyObject_GC_Track(ct); + return ct; +} + +static CTypeDescrObject * +ctypedescr_new_on_top(CTypeDescrObject *ct_base, const char *extra_text, + int extra_position) +{ + int base_name_len = strlen(ct_base->ct_name); + int extra_name_len = strlen(extra_text); + CTypeDescrObject *ct = ctypedescr_new(base_name_len + extra_name_len + 1); + char *p; + if (ct == NULL) + return NULL; + + Py_INCREF(ct_base); + ct->ct_itemdescr = ct_base; + ct->ct_name_position = ct_base->ct_name_position + extra_position; + + p = ct->ct_name; + memcpy(p, ct_base->ct_name, ct_base->ct_name_position); + p += ct_base->ct_name_position; + memcpy(p, extra_text, extra_name_len); + p += extra_name_len; + memcpy(p, ct_base->ct_name + ct_base->ct_name_position, + base_name_len - ct_base->ct_name_position + 1); + + return ct; +} + +static PyObject * +ctypedescr_repr(CTypeDescrObject *ct) +{ + return PyText_FromFormat("", ct->ct_name); +} + +static void +ctypedescr_dealloc(CTypeDescrObject *ct) +{ + PyObject_GC_UnTrack(ct); + if (ct->ct_weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *) ct); + + if (ct->ct_unique_key != NULL) { + /* revive dead object temporarily for DelItem */ + Py_REFCNT(ct) = 43; + PyDict_DelItem(unique_cache, ct->ct_unique_key); + assert(Py_REFCNT(ct) == 42); + Py_REFCNT(ct) = 0; + Py_DECREF(ct->ct_unique_key); + } + Py_XDECREF(ct->ct_itemdescr); + Py_XDECREF(ct->ct_stuff); + if (ct->ct_flags & CT_FUNCTIONPTR) + PyObject_Free(ct->ct_extra); + Py_TYPE(ct)->tp_free((PyObject *)ct); +} + +static int +ctypedescr_traverse(CTypeDescrObject *ct, visitproc visit, void *arg) +{ + Py_VISIT(ct->ct_itemdescr); + Py_VISIT(ct->ct_stuff); + return 0; +} + +static int +ctypedescr_clear(CTypeDescrObject *ct) +{ + Py_CLEAR(ct->ct_itemdescr); + Py_CLEAR(ct->ct_stuff); + return 0; +} + + +static PyObject *nosuchattr(const char *attr) +{ + PyErr_SetString(PyExc_AttributeError, attr); + return NULL; +} + +static PyObject *ctypeget_kind(CTypeDescrObject *ct, void *context) +{ + char *result; + if (ct->ct_flags & CT_PRIMITIVE_ANY) { + if (ct->ct_flags & CT_IS_ENUM) + result = "enum"; + else + result = "primitive"; + } + else if (ct->ct_flags & CT_POINTER) { + result = "pointer"; + } + else if (ct->ct_flags & CT_ARRAY) { + result = "array"; + } + else if (ct->ct_flags & CT_VOID) { + result = "void"; + } + else if (ct->ct_flags & CT_STRUCT) { + result = "struct"; + } + else if (ct->ct_flags & CT_UNION) { + result = "union"; + } + else if (ct->ct_flags & CT_FUNCTIONPTR) { + result = "function"; + } + else + result = "?"; + + return PyText_FromString(result); +} + +static PyObject *ctypeget_cname(CTypeDescrObject *ct, void *context) +{ + return PyText_FromString(ct->ct_name); +} + +static PyObject *ctypeget_item(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & (CT_POINTER | CT_ARRAY)) { + Py_INCREF(ct->ct_itemdescr); + return (PyObject *)ct->ct_itemdescr; + } + return nosuchattr("item"); +} + +static PyObject *ctypeget_length(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_ARRAY) { + if (ct->ct_length >= 0) { + return PyInt_FromSsize_t(ct->ct_length); + } + else { + Py_INCREF(Py_None); + return Py_None; + } + } + return nosuchattr("length"); +} + +static PyObject * +get_field_name(CTypeDescrObject *ct, CFieldObject *cf); /* forward */ + +/* returns 0 if the struct ctype is opaque, 1 if it is not, or -1 if + an exception occurs */ +#define force_lazy_struct(ct) \ + ((ct)->ct_stuff != NULL ? 1 : do_realize_lazy_struct(ct)) + +static int do_realize_lazy_struct(CTypeDescrObject *ct); +/* forward, implemented in realize_c_type.c */ + +static PyObject *ctypeget_fields(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & (CT_STRUCT | CT_UNION)) { + if (!(ct->ct_flags & CT_IS_OPAQUE)) { + CFieldObject *cf; + PyObject *res; + if (force_lazy_struct(ct) < 0) + return NULL; + res = PyList_New(0); + if (res == NULL) + return NULL; + for (cf = (CFieldObject *)ct->ct_extra; + cf != NULL; cf = cf->cf_next) { + PyObject *o = PyTuple_Pack(2, get_field_name(ct, cf), + (PyObject *)cf); + int err = (o != NULL) ? PyList_Append(res, o) : -1; + Py_XDECREF(o); + if (err < 0) { + Py_DECREF(res); + return NULL; + } + } + return res; + } + else { + Py_INCREF(Py_None); + return Py_None; + } + } + return nosuchattr("fields"); +} + +static PyObject *ctypeget_args(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_FUNCTIONPTR) { + PyObject *t = ct->ct_stuff; + return PyTuple_GetSlice(t, 2, PyTuple_GET_SIZE(t)); + } + return nosuchattr("args"); +} + +static PyObject *ctypeget_result(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_FUNCTIONPTR) { + PyObject *res = PyTuple_GetItem(ct->ct_stuff, 1); + Py_XINCREF(res); + return res; + } + return nosuchattr("result"); +} + +static PyObject *ctypeget_ellipsis(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_FUNCTIONPTR) { + PyObject *res = ct->ct_extra ? Py_False : Py_True; + Py_INCREF(res); + return res; + } + return nosuchattr("ellipsis"); +} + +static PyObject *ctypeget_abi(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_FUNCTIONPTR) { + PyObject *res = PyTuple_GetItem(ct->ct_stuff, 0); + Py_XINCREF(res); + return res; + } + return nosuchattr("abi"); +} + +static PyObject *ctypeget_elements(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_IS_ENUM) { + PyObject *res = PyTuple_GetItem(ct->ct_stuff, 1); + if (res) res = PyDict_Copy(res); + return res; + } + return nosuchattr("elements"); +} + +static PyObject *ctypeget_relements(CTypeDescrObject *ct, void *context) +{ + if (ct->ct_flags & CT_IS_ENUM) { + PyObject *res = PyTuple_GetItem(ct->ct_stuff, 0); + if (res) res = PyDict_Copy(res); + return res; + } + return nosuchattr("relements"); +} + +static PyGetSetDef ctypedescr_getsets[] = { + {"kind", (getter)ctypeget_kind, NULL, "kind"}, + {"cname", (getter)ctypeget_cname, NULL, "C name"}, + {"item", (getter)ctypeget_item, NULL, "pointer to, or array of"}, + {"length", (getter)ctypeget_length, NULL, "array length or None"}, + {"fields", (getter)ctypeget_fields, NULL, "struct or union fields"}, + {"args", (getter)ctypeget_args, NULL, "function argument types"}, + {"result", (getter)ctypeget_result, NULL, "function result type"}, + {"ellipsis", (getter)ctypeget_ellipsis, NULL, "function has '...'"}, + {"abi", (getter)ctypeget_abi, NULL, "function ABI"}, + {"elements", (getter)ctypeget_elements, NULL, "enum elements"}, + {"relements", (getter)ctypeget_relements, NULL, "enum elements, reverse"}, + {NULL} /* sentinel */ +}; + +static PyObject * +ctypedescr_dir(PyObject *ct, PyObject *noarg) +{ + int err; + struct PyGetSetDef *gsdef; + PyObject *res = PyList_New(0); + if (res == NULL) + return NULL; + + for (gsdef = ctypedescr_getsets; gsdef->name; gsdef++) { + PyObject *x = PyObject_GetAttrString(ct, gsdef->name); + if (x == NULL) { + PyErr_Clear(); + } + else { + Py_DECREF(x); + x = PyText_FromString(gsdef->name); + err = (x != NULL) ? PyList_Append(res, x) : -1; + Py_XDECREF(x); + if (err < 0) { + Py_DECREF(res); + return NULL; + } + } + } + return res; +} + +static PyMethodDef ctypedescr_methods[] = { + {"__dir__", ctypedescr_dir, METH_NOARGS}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject CTypeDescr_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CTypeDescr", + offsetof(CTypeDescrObject, ct_name), + sizeof(char), + (destructor)ctypedescr_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)ctypedescr_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)ctypedescr_traverse, /* tp_traverse */ + (inquiry)ctypedescr_clear, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(CTypeDescrObject, ct_weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + ctypedescr_methods, /* tp_methods */ + 0, /* tp_members */ + ctypedescr_getsets, /* tp_getset */ +}; + +/************************************************************/ + +static PyObject * +get_field_name(CTypeDescrObject *ct, CFieldObject *cf) +{ + Py_ssize_t i = 0; + PyObject *d_key, *d_value; + while (PyDict_Next(ct->ct_stuff, &i, &d_key, &d_value)) { + if (d_value == (PyObject *)cf) + return d_key; + } + Py_FatalError("_cffi_backend: get_field_name()"); + return NULL; +} + +static void +cfield_dealloc(CFieldObject *cf) +{ + Py_DECREF(cf->cf_type); + PyObject_Del(cf); +} + +#undef OFF +#define OFF(x) offsetof(CFieldObject, x) + +static PyMemberDef cfield_members[] = { + {"type", T_OBJECT, OFF(cf_type), READONLY}, + {"offset", T_PYSSIZET, OFF(cf_offset), READONLY}, + {"bitshift", T_SHORT, OFF(cf_bitshift), READONLY}, + {"bitsize", T_SHORT, OFF(cf_bitsize), READONLY}, + {"flags", T_UBYTE, OFF(cf_flags), READONLY}, + {NULL} /* Sentinel */ +}; +#undef OFF + +static PyTypeObject CField_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CField", + sizeof(CFieldObject), + 0, + (destructor)cfield_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + cfield_members, /* tp_members */ +}; + +/************************************************************/ + +static int +CDataObject_Or_PyFloat_Check(PyObject *ob) +{ + return (PyFloat_Check(ob) || + (CData_Check(ob) && + (((CDataObject *)ob)->c_type->ct_flags & CT_PRIMITIVE_FLOAT))); +} + +static PY_LONG_LONG +_my_PyLong_AsLongLong(PyObject *ob) +{ + /* (possibly) convert and cast a Python object to a long long. + Like PyLong_AsLongLong(), this version accepts a Python int too, and + does convertions from other types of objects. The difference is that + this version refuses floats. */ +#if PY_MAJOR_VERSION < 3 + if (PyInt_Check(ob)) { + return PyInt_AS_LONG(ob); + } + else +#endif + if (PyLong_Check(ob)) { + return PyLong_AsLongLong(ob); + } + else { + PyObject *io; + PY_LONG_LONG res; + PyNumberMethods *nb = ob->ob_type->tp_as_number; + + if (CDataObject_Or_PyFloat_Check(ob) || + nb == NULL || nb->nb_int == NULL) { + PyErr_SetString(PyExc_TypeError, "an integer is required"); + return -1; + } + io = (*nb->nb_int) (ob); + if (io == NULL) + return -1; + + if (PyIntOrLong_Check(io)) { + res = _my_PyLong_AsLongLong(io); + } + else { + PyErr_SetString(PyExc_TypeError, "integer conversion failed"); + res = -1; + } + Py_DECREF(io); + return res; + } +} + +static unsigned PY_LONG_LONG +_my_PyLong_AsUnsignedLongLong(PyObject *ob, int strict) +{ + /* (possibly) convert and cast a Python object to an unsigned long long. + Like PyLong_AsLongLong(), this version accepts a Python int too, and + does convertions from other types of objects. If 'strict', complains + with OverflowError and refuses floats. If '!strict', rounds floats + and masks the result. */ +#if PY_MAJOR_VERSION < 3 + if (PyInt_Check(ob)) { + long value1 = PyInt_AS_LONG(ob); + if (strict && value1 < 0) + goto negative; + return (unsigned PY_LONG_LONG)(PY_LONG_LONG)value1; + } + else +#endif + if (PyLong_Check(ob)) { + if (strict) { + if (_PyLong_Sign(ob) < 0) + goto negative; + return PyLong_AsUnsignedLongLong(ob); + } + else { + return PyLong_AsUnsignedLongLongMask(ob); + } + } + else { + PyObject *io; + unsigned PY_LONG_LONG res; + PyNumberMethods *nb = ob->ob_type->tp_as_number; + + if ((strict && CDataObject_Or_PyFloat_Check(ob)) || + nb == NULL || nb->nb_int == NULL) { + PyErr_SetString(PyExc_TypeError, "an integer is required"); + return (unsigned PY_LONG_LONG)-1; + } + io = (*nb->nb_int) (ob); + if (io == NULL) + return (unsigned PY_LONG_LONG)-1; + + if (PyIntOrLong_Check(io)) { + res = _my_PyLong_AsUnsignedLongLong(io, strict); + } + else { + PyErr_SetString(PyExc_TypeError, "integer conversion failed"); + res = (unsigned PY_LONG_LONG)-1; + } + Py_DECREF(io); + return res; + } + + negative: + PyErr_SetString(PyExc_OverflowError, + "can't convert negative number to unsigned"); + return (unsigned PY_LONG_LONG)-1; +} + +#define _read_raw_data(type) \ + do { \ + if (size == sizeof(type)) { \ + type r; \ + memcpy(&r, target, sizeof(type)); \ + return r; \ + } \ + } while(0) + +static PY_LONG_LONG +read_raw_signed_data(char *target, int size) +{ + _read_raw_data(signed char); + _read_raw_data(short); + _read_raw_data(int); + _read_raw_data(long); + _read_raw_data(PY_LONG_LONG); + Py_FatalError("read_raw_signed_data: bad integer size"); + return 0; +} + +static unsigned PY_LONG_LONG +read_raw_unsigned_data(char *target, int size) +{ + _read_raw_data(unsigned char); + _read_raw_data(unsigned short); + _read_raw_data(unsigned int); + _read_raw_data(unsigned long); + _read_raw_data(unsigned PY_LONG_LONG); + Py_FatalError("read_raw_unsigned_data: bad integer size"); + return 0; +} + +#ifdef __GNUC__ +/* This is a workaround for what I think is a GCC bug on several + platforms. See issue #378. */ +__attribute__((noinline)) +#endif +void _cffi_memcpy(char *target, const void *src, size_t size) +{ + memcpy(target, src, size); +} + +#define _write_raw_data(type) \ + do { \ + if (size == sizeof(type)) { \ + type r = (type)source; \ + _cffi_memcpy(target, &r, sizeof(type)); \ + return; \ + } \ + } while(0) + +static void +write_raw_integer_data(char *target, unsigned PY_LONG_LONG source, int size) +{ + _write_raw_data(unsigned char); + _write_raw_data(unsigned short); + _write_raw_data(unsigned int); + _write_raw_data(unsigned long); + _write_raw_data(unsigned PY_LONG_LONG); + Py_FatalError("write_raw_integer_data: bad integer size"); +} + +static double +read_raw_float_data(char *target, int size) +{ + _read_raw_data(float); + _read_raw_data(double); + Py_FatalError("read_raw_float_data: bad float size"); + return 0; +} + +static long double +read_raw_longdouble_data(char *target) +{ + int size = sizeof(long double); + _read_raw_data(long double); + Py_FatalError("read_raw_longdouble_data: bad long double size"); + return 0; +} + +static Py_complex +read_raw_complex_data(char *target, int size) +{ + Py_complex r = {0.0, 0.0}; + if (size == 2*sizeof(float)) { + float real_part, imag_part; + memcpy(&real_part, target + 0, sizeof(float)); + memcpy(&imag_part, target + sizeof(float), sizeof(float)); + r.real = real_part; + r.imag = imag_part; + return r; + } + if (size == 2*sizeof(double)) { + memcpy(&r, target, 2*sizeof(double)); + return r; + } + Py_FatalError("read_raw_complex_data: bad complex size"); + return r; +} + +static void +write_raw_float_data(char *target, double source, int size) +{ + _write_raw_data(float); + _write_raw_data(double); + Py_FatalError("write_raw_float_data: bad float size"); +} + +static void +write_raw_longdouble_data(char *target, long double source) +{ + int size = sizeof(long double); + _write_raw_data(long double); +} + +#define _write_raw_complex_data(type) \ + do { \ + if (size == 2*sizeof(type)) { \ + type r = (type)source.real; \ + type i = (type)source.imag; \ + _cffi_memcpy(target, &r, sizeof(type)); \ + _cffi_memcpy(target+sizeof(type), &i, sizeof(type)); \ + return; \ + } \ + } while(0) + +static void +write_raw_complex_data(char *target, Py_complex source, int size) +{ + _write_raw_complex_data(float); + _write_raw_complex_data(double); + Py_FatalError("write_raw_complex_data: bad complex size"); +} + +static PyObject * +new_simple_cdata(char *data, CTypeDescrObject *ct) +{ + CDataObject *cd = PyObject_New(CDataObject, &CData_Type); + if (cd == NULL) + return NULL; + Py_INCREF(ct); + cd->c_data = data; + cd->c_type = ct; + cd->c_weakreflist = NULL; + return (PyObject *)cd; +} + +static PyObject * +new_sized_cdata(char *data, CTypeDescrObject *ct, Py_ssize_t length) +{ + CDataObject_own_length *scd; + + scd = (CDataObject_own_length *)PyObject_Malloc( + offsetof(CDataObject_own_length, alignment)); + if (PyObject_Init((PyObject *)scd, &CData_Type) == NULL) + return NULL; + Py_INCREF(ct); + scd->head.c_type = ct; + scd->head.c_data = data; + scd->head.c_weakreflist = NULL; + scd->length = length; + return (PyObject *)scd; +} + +static CDataObject *_new_casted_primitive(CTypeDescrObject *ct); /*forward*/ + +static PyObject * +convert_to_object(char *data, CTypeDescrObject *ct) +{ + if (!(ct->ct_flags & CT_PRIMITIVE_ANY)) { + /* non-primitive types (check done just for performance) */ + if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) { + char *ptrdata = *(char **)data; + /*READ(data, sizeof(char *))*/ + return new_simple_cdata(ptrdata, ct); + } + else if (ct->ct_flags & CT_IS_OPAQUE) { + PyErr_Format(PyExc_TypeError, "cdata '%s' is opaque", + ct->ct_name); + return NULL; + } + else if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { + return new_simple_cdata(data, ct); + } + else if (ct->ct_flags & CT_ARRAY) { + if (ct->ct_length < 0) { + /* we can't return a here, because we don't + know the length to give it. As a compromize, returns + in this case. */ + ct = (CTypeDescrObject *)ct->ct_stuff; + } + return new_simple_cdata(data, ct); + } + } + else if (ct->ct_flags & CT_PRIMITIVE_SIGNED) { + PY_LONG_LONG value; + /*READ(data, ct->ct_size)*/ + value = read_raw_signed_data(data, ct->ct_size); + if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG) + return PyInt_FromLong((long)value); + else + return PyLong_FromLongLong(value); + } + else if (ct->ct_flags & CT_PRIMITIVE_UNSIGNED) { + unsigned PY_LONG_LONG value; + /*READ(data, ct->ct_size)*/ + value = read_raw_unsigned_data(data, ct->ct_size); + + if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG) { + if (ct->ct_flags & CT_IS_BOOL) { + PyObject *x; + switch ((int)value) { + case 0: x = Py_False; break; + case 1: x = Py_True; break; + default: + PyErr_Format(PyExc_ValueError, + "got a _Bool of value %d, expected 0 or 1", + (int)value); + return NULL; + } + Py_INCREF(x); + return x; + } + return PyInt_FromLong((long)value); + } + else + return PyLong_FromUnsignedLongLong(value); + } + else if (ct->ct_flags & CT_PRIMITIVE_FLOAT) { + /*READ(data, ct->ct_size)*/ + if (!(ct->ct_flags & CT_IS_LONGDOUBLE)) { + double value = read_raw_float_data(data, ct->ct_size); + return PyFloat_FromDouble(value); + } + else { + long double value = read_raw_longdouble_data(data); + CDataObject *cd = _new_casted_primitive(ct); + if (cd != NULL) + write_raw_longdouble_data(cd->c_data, value); + return (PyObject *)cd; + } + } + else if (ct->ct_flags & CT_PRIMITIVE_CHAR) { + /*READ(data, ct->ct_size)*/ + switch (ct->ct_size) { + case sizeof(char): + return PyBytes_FromStringAndSize(data, 1); + case 2: + return _my_PyUnicode_FromChar16((cffi_char16_t *)data, 1); + case 4: + return _my_PyUnicode_FromChar32((cffi_char32_t *)data, 1); + } + } + else if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) { + Py_complex value = read_raw_complex_data(data, ct->ct_size); + return PyComplex_FromCComplex(value); + } + + PyErr_Format(PyExc_SystemError, + "convert_to_object: '%s'", ct->ct_name); + return NULL; +} + +static PyObject * +convert_to_object_bitfield(char *data, CFieldObject *cf) +{ + CTypeDescrObject *ct = cf->cf_type; + /*READ(data, ct->ct_size)*/ + + if (ct->ct_flags & CT_PRIMITIVE_SIGNED) { + unsigned PY_LONG_LONG value, valuemask, shiftforsign; + PY_LONG_LONG result; + + value = (unsigned PY_LONG_LONG)read_raw_signed_data(data, ct->ct_size); + valuemask = (1ULL << cf->cf_bitsize) - 1ULL; + shiftforsign = 1ULL << (cf->cf_bitsize - 1); + value = ((value >> cf->cf_bitshift) + shiftforsign) & valuemask; + result = ((PY_LONG_LONG)value) - (PY_LONG_LONG)shiftforsign; + + if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG) + return PyInt_FromLong((long)result); + else + return PyLong_FromLongLong(result); + } + else { + unsigned PY_LONG_LONG value, valuemask; + + value = read_raw_unsigned_data(data, ct->ct_size); + valuemask = (1ULL << cf->cf_bitsize) - 1ULL; + value = (value >> cf->cf_bitshift) & valuemask; + + if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG) + return PyInt_FromLong((long)value); + else + return PyLong_FromUnsignedLongLong(value); + } +} + +static int _convert_overflow(PyObject *init, const char *ct_name) +{ + PyObject *s; + if (PyErr_Occurred()) /* already an exception pending */ + return -1; + s = PyObject_Str(init); + if (s == NULL) + return -1; + PyErr_Format(PyExc_OverflowError, "integer %s does not fit '%s'", + PyText_AS_UTF8(s), ct_name); + Py_DECREF(s); + return -1; +} + +static int _convert_to_char(PyObject *init) +{ + if (PyBytes_Check(init) && PyBytes_GET_SIZE(init) == 1) { + return (unsigned char)(PyBytes_AS_STRING(init)[0]); + } + if (CData_Check(init) && + (((CDataObject *)init)->c_type->ct_flags & CT_PRIMITIVE_CHAR) && + (((CDataObject *)init)->c_type->ct_size == sizeof(char))) { + char *data = ((CDataObject *)init)->c_data; + /*READ(data, 1)*/ + return *(unsigned char *)data; + } + PyErr_Format(PyExc_TypeError, + "initializer for ctype 'char' must be a "STR_OR_BYTES + " of length 1, not %.200s", Py_TYPE(init)->tp_name); + return -1; +} + +static cffi_char16_t _convert_to_char16_t(PyObject *init) +{ + char err_got[80]; + err_got[0] = 0; + + if (PyUnicode_Check(init)) { + cffi_char16_t ordinal; + if (_my_PyUnicode_AsSingleChar16(init, &ordinal, err_got) == 0) + return ordinal; + } + if (CData_Check(init) && + (((CDataObject *)init)->c_type->ct_flags & CT_PRIMITIVE_CHAR) && + (((CDataObject *)init)->c_type->ct_size == 2)) { + char *data = ((CDataObject *)init)->c_data; + /*READ(data, 2)*/ + return *(cffi_char16_t *)data; + } + PyErr_Format(PyExc_TypeError, + "initializer for ctype 'char16_t' must be a unicode string " + "of length 1, not %.200s", + err_got[0] == 0 ? Py_TYPE(init)->tp_name : err_got); + return (cffi_char16_t)-1; +} + +static cffi_char32_t _convert_to_char32_t(PyObject *init) +{ + char err_got[80]; + err_got[0] = 0; + + if (PyUnicode_Check(init)) { + cffi_char32_t ordinal; + if (_my_PyUnicode_AsSingleChar32(init, &ordinal, err_got) == 0) + return ordinal; + } + if (CData_Check(init) && + (((CDataObject *)init)->c_type->ct_flags & CT_PRIMITIVE_CHAR) && + (((CDataObject *)init)->c_type->ct_size == 4)) { + char *data = ((CDataObject *)init)->c_data; + /*READ(data, 4)*/ + return *(cffi_char32_t *)data; + } + PyErr_Format(PyExc_TypeError, + "initializer for ctype 'char32_t' must be a unicode string " + "of length 1, not %.200s", + err_got[0] == 0 ? Py_TYPE(init)->tp_name : err_got); + return (cffi_char32_t)-1; +} + +static int _convert_error(PyObject *init, CTypeDescrObject *ct, + const char *expected) +{ + if (CData_Check(init)) { + CTypeDescrObject *ct2 = ((CDataObject *)init)->c_type; + if (strcmp(ct->ct_name, ct2->ct_name) != 0) + PyErr_Format(PyExc_TypeError, + "initializer for ctype '%s' must be a %s, " + "not cdata '%s'", + ct->ct_name, expected, ct2->ct_name); + else if (ct != ct2) { + /* in case we'd give the error message "initializer for + ctype 'A' must be a pointer to same type, not cdata + 'B'", but with A=B, then give instead a different error + message to try to clear up the confusion */ + PyErr_Format(PyExc_TypeError, + "initializer for ctype '%s' appears indeed to be '%s'," + " but the types are different (check that you are not" + " e.g. mixing up different ffi instances)", + ct->ct_name, ct2->ct_name); + } + else + { + PyErr_Format(PyExc_SystemError, + "initializer for ctype '%s' is correct, but we get " + "an internal mismatch--please report a bug", + ct->ct_name); + } + } + else + PyErr_Format(PyExc_TypeError, + "initializer for ctype '%s' must be a %s, " + "not %.200s", + ct->ct_name, expected, Py_TYPE(init)->tp_name); + return -1; +} + +static int /* forward */ +convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init); +static int /* forward */ +convert_from_object_bitfield(char *data, CFieldObject *cf, PyObject *init); + +static Py_ssize_t +get_new_array_length(CTypeDescrObject *ctitem, PyObject **pvalue) +{ + PyObject *value = *pvalue; + + if (PyList_Check(value) || PyTuple_Check(value)) { + return PySequence_Fast_GET_SIZE(value); + } + else if (PyBytes_Check(value)) { + /* from a string, we add the null terminator */ + return PyBytes_GET_SIZE(value) + 1; + } + else if (PyUnicode_Check(value)) { + /* from a unicode, we add the null terminator */ + int length; + if (ctitem->ct_size == 2) + length = _my_PyUnicode_SizeAsChar16(value); + else + length = _my_PyUnicode_SizeAsChar32(value); + return length + 1; + } + else { + Py_ssize_t explicitlength; + explicitlength = PyNumber_AsSsize_t(value, PyExc_OverflowError); + if (explicitlength < 0) { + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) + PyErr_Format(PyExc_TypeError, + "expected new array length or list/tuple/str, " + "not %.200s", Py_TYPE(value)->tp_name); + } + else + PyErr_SetString(PyExc_ValueError, "negative array length"); + return -1; + } + *pvalue = Py_None; + return explicitlength; + } +} + +static int +convert_field_from_object(char *data, CFieldObject *cf, PyObject *value) +{ + data += cf->cf_offset; + if (cf->cf_bitshift >= 0) + return convert_from_object_bitfield(data, cf, value); + else + return convert_from_object(data, cf->cf_type, value); +} + +static int +convert_vfield_from_object(char *data, CFieldObject *cf, PyObject *value, + Py_ssize_t *optvarsize) +{ + /* a special case for var-sized C99 arrays */ + if ((cf->cf_type->ct_flags & CT_ARRAY) && cf->cf_type->ct_size < 0) { + Py_ssize_t varsizelength = get_new_array_length( + cf->cf_type->ct_itemdescr, &value); + if (varsizelength < 0) + return -1; + if (optvarsize != NULL) { + /* in this mode, the only purpose of this function is to compute + the real size of the structure from a var-sized C99 array */ + Py_ssize_t size, itemsize; + assert(data == NULL); + itemsize = cf->cf_type->ct_itemdescr->ct_size; + size = ADD_WRAPAROUND(cf->cf_offset, + MUL_WRAPAROUND(itemsize, varsizelength)); + if (size < 0 || + ((size - cf->cf_offset) / itemsize) != varsizelength) { + PyErr_SetString(PyExc_OverflowError, + "array size would overflow a Py_ssize_t"); + return -1; + } + if (size > *optvarsize) + *optvarsize = size; + return 0; + } + /* if 'value' was only an integer, get_new_array_length() returns + it and convert 'value' to be None. Detect if this was the case, + and if so, stop here, leaving the content uninitialized + (it should be zero-initialized from somewhere else). */ + if (value == Py_None) + return 0; + } + if (optvarsize == NULL) + return convert_field_from_object(data, cf, value); + else + return 0; +} + +static int +must_be_array_of_zero_or_one(const char *data, Py_ssize_t n) +{ + Py_ssize_t i; + for (i = 0; i < n; i++) { + if (((unsigned char)data[i]) > 1) { + PyErr_SetString(PyExc_ValueError, + "an array of _Bool can only contain \\x00 or \\x01"); + return -1; + } + } + return 0; +} + +static Py_ssize_t +get_array_length(CDataObject *cd) +{ + if (cd->c_type->ct_length < 0) + return ((CDataObject_own_length *)cd)->length; + else + return cd->c_type->ct_length; +} + +static int +convert_array_from_object(char *data, CTypeDescrObject *ct, PyObject *init) +{ + /* used by convert_from_object(), and also to decode lists/tuples/unicodes + passed as function arguments. 'ct' is an CT_ARRAY in the first case + and a CT_POINTER in the second case. */ + const char *expected; + CTypeDescrObject *ctitem = ct->ct_itemdescr; + + if (PyList_Check(init) || PyTuple_Check(init)) { + PyObject **items; + Py_ssize_t i, n; + n = PySequence_Fast_GET_SIZE(init); + if (ct->ct_length >= 0 && n > ct->ct_length) { + PyErr_Format(PyExc_IndexError, + "too many initializers for '%s' (got %zd)", + ct->ct_name, n); + return -1; + } + items = PySequence_Fast_ITEMS(init); + for (i=0; ict_size; + } + return 0; + } + else if ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) || + ((ctitem->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_UNSIGNED)) + && (ctitem->ct_size == sizeof(char)))) { + if (ctitem->ct_size == sizeof(char)) { + char *srcdata; + Py_ssize_t n; + if (!PyBytes_Check(init)) { + expected = STR_OR_BYTES" or list or tuple"; + goto cannot_convert; + } + n = PyBytes_GET_SIZE(init); + if (ct->ct_length >= 0 && n > ct->ct_length) { + PyErr_Format(PyExc_IndexError, + "initializer "STR_OR_BYTES" is too long for '%s' " + "(got %zd characters)", ct->ct_name, n); + return -1; + } + if (n != ct->ct_length) + n++; + srcdata = PyBytes_AS_STRING(init); + if (ctitem->ct_flags & CT_IS_BOOL) + if (must_be_array_of_zero_or_one(srcdata, n) < 0) + return -1; + memcpy(data, srcdata, n); + return 0; + } + else { + Py_ssize_t n; + if (!PyUnicode_Check(init)) { + expected = "unicode or list or tuple"; + goto cannot_convert; + } + + if (ctitem->ct_size == 4) + n = _my_PyUnicode_SizeAsChar32(init); + else + n = _my_PyUnicode_SizeAsChar16(init); + + if (ct->ct_length >= 0 && n > ct->ct_length) { + PyErr_Format(PyExc_IndexError, + "initializer unicode is too long for '%s' " + "(got %zd characters)", ct->ct_name, n); + return -1; + } + if (n != ct->ct_length) + n++; + if (ctitem->ct_size == 4) + return _my_PyUnicode_AsChar32(init, (cffi_char32_t *)data, n); + else + return _my_PyUnicode_AsChar16(init, (cffi_char16_t *)data, n); + } + } + else { + expected = "list or tuple"; + goto cannot_convert; + } + + cannot_convert: + if ((ct->ct_flags & CT_ARRAY) && CData_Check(init)) + { + CDataObject *cd = (CDataObject *)init; + if (cd->c_type == ct) + { + Py_ssize_t n = get_array_length(cd); + memcpy(data, cd->c_data, n * ctitem->ct_size); + return 0; + } + } + return _convert_error(init, ct, expected); +} + +static int +convert_struct_from_object(char *data, CTypeDescrObject *ct, PyObject *init, + Py_ssize_t *optvarsize) +{ + /* does not accept 'init' being already a CData */ + const char *expected; + + if (force_lazy_struct(ct) <= 0) { + if (!PyErr_Occurred()) + PyErr_Format(PyExc_TypeError, "'%s' is opaque", ct->ct_name); + return -1; + } + + if (PyList_Check(init) || PyTuple_Check(init)) { + PyObject **items = PySequence_Fast_ITEMS(init); + Py_ssize_t i, n = PySequence_Fast_GET_SIZE(init); + CFieldObject *cf = (CFieldObject *)ct->ct_extra; + + for (i=0; icf_flags & BF_IGNORE_IN_CTOR)) + cf = cf->cf_next; + if (cf == NULL) { + PyErr_Format(PyExc_ValueError, + "too many initializers for '%s' (got %zd)", + ct->ct_name, n); + return -1; + } + if (convert_vfield_from_object(data, cf, items[i], optvarsize) < 0) + return -1; + cf = cf->cf_next; + } + return 0; + } + if (PyDict_Check(init)) { + PyObject *d_key, *d_value; + Py_ssize_t i = 0; + CFieldObject *cf; + + while (PyDict_Next(init, &i, &d_key, &d_value)) { + cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, d_key); + if (cf == NULL) { + PyErr_SetObject(PyExc_KeyError, d_key); + return -1; + } + if (convert_vfield_from_object(data, cf, d_value, optvarsize) < 0) + return -1; + } + return 0; + } + expected = optvarsize == NULL ? "list or tuple or dict or struct-cdata" + : "list or tuple or dict"; + return _convert_error(init, ct, expected); +} + +#ifdef __GNUC__ +# if __GNUC__ >= 4 +/* Don't go inlining this huge function. Needed because occasionally + it gets inlined in places where is causes a warning: call to + __builtin___memcpy_chk will always overflow destination buffer + (which is places where the 'ct' should never represent such a large + primitive type anyway). */ +__attribute__((noinline)) +# endif +#endif +static int +convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init) +{ + const char *expected; + char buf[sizeof(PY_LONG_LONG)]; + + /*if (ct->ct_size > 0)*/ + /*WRITE(data, ct->ct_size)*/ + + if (ct->ct_flags & CT_ARRAY) { + return convert_array_from_object(data, ct, init); + } + if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) { + char *ptrdata; + CTypeDescrObject *ctinit; + + if (!CData_Check(init)) { + expected = "cdata pointer"; + goto cannot_convert; + } + ctinit = ((CDataObject *)init)->c_type; + if (!(ctinit->ct_flags & (CT_POINTER|CT_FUNCTIONPTR))) { + if (ctinit->ct_flags & CT_ARRAY) + ctinit = (CTypeDescrObject *)ctinit->ct_stuff; + else { + expected = "pointer or array"; + goto cannot_convert; + } + } + if (ctinit != ct) { + int combined_flags = ct->ct_flags | ctinit->ct_flags; + if (combined_flags & CT_IS_VOID_PTR) + ; /* accept "void *" as either source or target */ + else if (combined_flags & CT_IS_VOIDCHAR_PTR) { + /* for backward compatibility, accept "char *" as either + source of target. This is not what C does, though, + so emit a warning that will eventually turn into an + error. The warning is turned off if both types are + pointers to single bytes. */ + char *msg = (ct->ct_flags & CT_IS_VOIDCHAR_PTR ? + "implicit cast to 'char *' from a different pointer type: " + "will be forbidden in the future (check that the types " + "are as you expect; use an explicit ffi.cast() if they " + "are correct)" : + "implicit cast from 'char *' to a different pointer type: " + "will be forbidden in the future (check that the types " + "are as you expect; use an explicit ffi.cast() if they " + "are correct)"); + if ((ct->ct_flags & ctinit->ct_flags & CT_POINTER) && + ct->ct_itemdescr->ct_size == 1 && + ctinit->ct_itemdescr->ct_size == 1) { + /* no warning */ + } + else if (PyErr_WarnEx(PyExc_UserWarning, msg, 1)) + return -1; + } + else { + expected = "pointer to same type"; + goto cannot_convert; + } + } + ptrdata = ((CDataObject *)init)->c_data; + + *(char **)data = ptrdata; + return 0; + } + if (ct->ct_flags & CT_PRIMITIVE_SIGNED) { + PY_LONG_LONG value = _my_PyLong_AsLongLong(init); + if (value == -1 && PyErr_Occurred()) + return -1; + write_raw_integer_data(buf, value, ct->ct_size); + if (value != read_raw_signed_data(buf, ct->ct_size)) + goto overflow; + write_raw_integer_data(data, value, ct->ct_size); + return 0; + } + if (ct->ct_flags & CT_PRIMITIVE_UNSIGNED) { + unsigned PY_LONG_LONG value = _my_PyLong_AsUnsignedLongLong(init, 1); + if (value == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) + return -1; + if (ct->ct_flags & CT_IS_BOOL) { + if (value > 1ULL) /* value != 0 && value != 1 */ + goto overflow; + } + else { + write_raw_integer_data(buf, value, ct->ct_size); + if (value != read_raw_unsigned_data(buf, ct->ct_size)) + goto overflow; + } + write_raw_integer_data(data, value, ct->ct_size); + return 0; + } + if (ct->ct_flags & CT_PRIMITIVE_FLOAT) { + double value; + if ((ct->ct_flags & CT_IS_LONGDOUBLE) && + CData_Check(init) && + (((CDataObject *)init)->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + long double lvalue; + char *initdata = ((CDataObject *)init)->c_data; + /*READ(initdata, sizeof(long double))*/ + lvalue = read_raw_longdouble_data(initdata); + write_raw_longdouble_data(data, lvalue); + return 0; + } + value = PyFloat_AsDouble(init); + if (value == -1.0 && PyErr_Occurred()) + return -1; + if (!(ct->ct_flags & CT_IS_LONGDOUBLE)) + write_raw_float_data(data, value, ct->ct_size); + else + write_raw_longdouble_data(data, (long double)value); + return 0; + } + if (ct->ct_flags & CT_PRIMITIVE_CHAR) { + switch (ct->ct_size) { + case sizeof(char): { + int res = _convert_to_char(init); + if (res < 0) + return -1; + data[0] = res; + return 0; + } + case 2: { + cffi_char16_t res = _convert_to_char16_t(init); + if (res == (cffi_char16_t)-1 && PyErr_Occurred()) + return -1; + *(cffi_char16_t *)data = res; + return 0; + } + case 4: { + cffi_char32_t res = _convert_to_char32_t(init); + if (res == (cffi_char32_t)-1 && PyErr_Occurred()) + return -1; + *(cffi_char32_t *)data = res; + return 0; + } + } + } + if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { + + if (CData_Check(init)) { + if (((CDataObject *)init)->c_type == ct && ct->ct_size >= 0) { + memcpy(data, ((CDataObject *)init)->c_data, ct->ct_size); + return 0; + } + } + return convert_struct_from_object(data, ct, init, NULL); + } + if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) { + Py_complex value = PyComplex_AsCComplex(init); + if (PyErr_Occurred()) + return -1; + write_raw_complex_data(data, value, ct->ct_size); + return 0; + } + PyErr_Format(PyExc_SystemError, + "convert_from_object: '%s'", ct->ct_name); + return -1; + + overflow: + return _convert_overflow(init, ct->ct_name); + + cannot_convert: + return _convert_error(init, ct, expected); +} + +static int +convert_from_object_bitfield(char *data, CFieldObject *cf, PyObject *init) +{ + CTypeDescrObject *ct = cf->cf_type; + PY_LONG_LONG fmin, fmax, value = PyLong_AsLongLong(init); + unsigned PY_LONG_LONG rawfielddata, rawvalue, rawmask; + if (value == -1 && PyErr_Occurred()) + return -1; + + if (ct->ct_flags & CT_PRIMITIVE_SIGNED) { + fmin = -(1LL << (cf->cf_bitsize-1)); + fmax = (1LL << (cf->cf_bitsize-1)) - 1LL; + if (fmax == 0) + fmax = 1; /* special case to let "int x:1" receive "1" */ + } + else { + fmin = 0LL; + fmax = (PY_LONG_LONG)((1ULL << cf->cf_bitsize) - 1ULL); + } + if (value < fmin || value > fmax) { + /* phew, PyErr_Format does not support "%lld" in Python 2.6 */ + PyObject *svalue = NULL, *sfmin = NULL, *sfmax = NULL; + PyObject *lfmin = NULL, *lfmax = NULL; + svalue = PyObject_Str(init); + if (svalue == NULL) goto skip; + lfmin = PyLong_FromLongLong(fmin); + if (lfmin == NULL) goto skip; + sfmin = PyObject_Str(lfmin); + if (sfmin == NULL) goto skip; + lfmax = PyLong_FromLongLong(fmax); + if (lfmax == NULL) goto skip; + sfmax = PyObject_Str(lfmax); + if (sfmax == NULL) goto skip; + PyErr_Format(PyExc_OverflowError, + "value %s outside the range allowed by the " + "bit field width: %s <= x <= %s", + PyText_AS_UTF8(svalue), + PyText_AS_UTF8(sfmin), + PyText_AS_UTF8(sfmax)); + skip: + Py_XDECREF(svalue); + Py_XDECREF(sfmin); + Py_XDECREF(sfmax); + Py_XDECREF(lfmin); + Py_XDECREF(lfmax); + return -1; + } + + rawmask = ((1ULL << cf->cf_bitsize) - 1ULL) << cf->cf_bitshift; + rawvalue = ((unsigned PY_LONG_LONG)value) << cf->cf_bitshift; + /*WRITE(data, ct->ct_size)*/ + rawfielddata = read_raw_unsigned_data(data, ct->ct_size); + rawfielddata = (rawfielddata & ~rawmask) | (rawvalue & rawmask); + write_raw_integer_data(data, rawfielddata, ct->ct_size); + return 0; +} + +static int +get_alignment(CTypeDescrObject *ct) +{ + int align; + retry: + if ((ct->ct_flags & (CT_PRIMITIVE_ANY|CT_STRUCT|CT_UNION)) && + !(ct->ct_flags & CT_IS_OPAQUE)) { + align = ct->ct_length; + if (align == -1 && (ct->ct_flags & CT_LAZY_FIELD_LIST)) { + force_lazy_struct(ct); + align = ct->ct_length; + } + } + else if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) { + struct aligncheck_ptr { char x; char *y; }; + align = offsetof(struct aligncheck_ptr, y); + } + else if (ct->ct_flags & CT_ARRAY) { + ct = ct->ct_itemdescr; + goto retry; + } + else { + PyErr_Format(PyExc_ValueError, "ctype '%s' is of unknown alignment", + ct->ct_name); + return -1; + } + + if ((align < 1) || (align & (align-1))) { + PyErr_Format(PyExc_SystemError, + "found for ctype '%s' bogus alignment '%d'", + ct->ct_name, align); + return -1; + } + return align; +} + +static void cdata_dealloc(CDataObject *cd) +{ + if (cd->c_weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *) cd); + + Py_DECREF(cd->c_type); +#ifndef CFFI_MEM_LEAK /* never release anything, tests only */ + Py_TYPE(cd)->tp_free((PyObject *)cd); +#endif +} + +static void cdataowning_dealloc(CDataObject *cd) +{ + assert(!(cd->c_type->ct_flags & (CT_IS_VOID_PTR | CT_FUNCTIONPTR))); + + if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) { + Py_DECREF(((CDataObject_own_structptr *)cd)->structobj); + } +#if defined(CFFI_MEM_DEBUG) || defined(CFFI_MEM_LEAK) + if (cd->c_type->ct_flags & (CT_PRIMITIVE_ANY | CT_STRUCT | CT_UNION)) { + assert(cd->c_type->ct_size >= 0); + memset(cd->c_data, 0xDD, cd->c_type->ct_size); + } + else if (cd->c_type->ct_flags & CT_ARRAY) { + Py_ssize_t x = get_array_length(cd); + assert(x >= 0); + x *= cd->c_type->ct_itemdescr->ct_size; + assert(x >= 0); + memset(cd->c_data, 0xDD, x); + } +#endif + cdata_dealloc(cd); +} + +static void cdataowninggc_dealloc(CDataObject *cd) +{ + assert(!(cd->c_type->ct_flags & (CT_IS_PTR_TO_OWNED | + CT_PRIMITIVE_ANY | + CT_STRUCT | CT_UNION))); + PyObject_GC_UnTrack(cd); + + if (cd->c_type->ct_flags & CT_IS_VOID_PTR) { /* a handle */ + PyObject *x = ((CDataObject_own_structptr *)cd)->structobj; + Py_DECREF(x); + } + else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) { /* a callback */ + ffi_closure *closure = ((CDataObject_closure *)cd)->closure; + PyObject *args = (PyObject *)(closure->user_data); + Py_XDECREF(args); +#ifdef CFFI_TRUST_LIBFFI + ffi_closure_free(closure); +#else + cffi_closure_free(closure); +#endif + } + else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */ + Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview; + PyBuffer_Release(view); + PyObject_Free(view); + } + cdata_dealloc(cd); +} + +static int cdataowninggc_traverse(CDataObject *cd, visitproc visit, void *arg) +{ + if (cd->c_type->ct_flags & CT_IS_VOID_PTR) { /* a handle */ + PyObject *x = ((CDataObject_own_structptr *)cd)->structobj; + Py_VISIT(x); + } + else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) { /* a callback */ + ffi_closure *closure = ((CDataObject_closure *)cd)->closure; + PyObject *args = (PyObject *)(closure->user_data); + Py_VISIT(args); + } + else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */ + Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview; + Py_VISIT(view->obj); + } + return 0; +} + +static int cdataowninggc_clear(CDataObject *cd) +{ + if (cd->c_type->ct_flags & CT_IS_VOID_PTR) { /* a handle */ + CDataObject_own_structptr *cd1 = (CDataObject_own_structptr *)cd; + PyObject *x = cd1->structobj; + Py_INCREF(Py_None); + cd1->structobj = Py_None; + Py_DECREF(x); + } + else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) { /* a callback */ + ffi_closure *closure = ((CDataObject_closure *)cd)->closure; + PyObject *args = (PyObject *)(closure->user_data); + closure->user_data = NULL; + Py_XDECREF(args); + } + else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */ + Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview; + PyBuffer_Release(view); + } + return 0; +} + +/* forward */ +static void _my_PyErr_WriteUnraisable(PyObject *t, PyObject *v, PyObject *tb, + char *objdescr, PyObject *obj, + char *extra_error_line); + + +static void gcp_finalize(PyObject *destructor, PyObject *origobj) +{ + /* NOTE: this decrements the reference count of the two arguments */ + + if (destructor != NULL) { + PyObject *result; + PyObject *error_type, *error_value, *error_traceback; + + /* Save the current exception */ + PyErr_Fetch(&error_type, &error_value, &error_traceback); + + result = PyObject_CallFunctionObjArgs(destructor, origobj, NULL); + if (result != NULL) { + Py_DECREF(result); + } + else { + PyObject *t, *v, *tb; + PyErr_Fetch(&t, &v, &tb); + /* Don't use error capture here, because it is very much + * like errors at __del__(), and these ones are not captured + * either */ + /* ecap = _cffi_start_error_capture(); */ + _my_PyErr_WriteUnraisable(t, v, tb, "From callback for ffi.gc ", + origobj, NULL); + /* _cffi_stop_error_capture(ecap); */ + } + Py_DECREF(destructor); + + /* Restore the saved exception */ + PyErr_Restore(error_type, error_value, error_traceback); + } + Py_XDECREF(origobj); +} + +static void cdatagcp_finalize(CDataObject_gcp *cd) +{ + PyObject *destructor = cd->destructor; + PyObject *origobj = cd->origobj; + cd->destructor = NULL; + cd->origobj = NULL; + gcp_finalize(destructor, origobj); +} + +static void cdatagcp_dealloc(CDataObject_gcp *cd) +{ + PyObject *destructor = cd->destructor; + PyObject *origobj = cd->origobj; + cdata_dealloc((CDataObject *)cd); + + gcp_finalize(destructor, origobj); +} + +static int cdatagcp_traverse(CDataObject_gcp *cd, visitproc visit, void *arg) +{ + Py_VISIT(cd->destructor); + Py_VISIT(cd->origobj); + return 0; +} + +static PyObject *cdata_float(CDataObject *cd); /*forward*/ + +static PyObject *convert_cdata_to_enum_string(CDataObject *cd, int both) +{ + PyObject *d_key, *d_value; + CTypeDescrObject *ct = cd->c_type; + + assert(ct->ct_flags & CT_IS_ENUM); + d_key = convert_to_object(cd->c_data, ct); + if (d_key == NULL) + return NULL; + + d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 1), d_key); + if (d_value != NULL) { + if (both) { + PyObject *o = PyObject_Str(d_key); + if (o == NULL) + d_value = NULL; + else { + d_value = PyText_FromFormat("%s: %s", + PyText_AS_UTF8(o), + PyText_AS_UTF8(d_value)); + Py_DECREF(o); + } + } + else + Py_INCREF(d_value); + } + else + d_value = PyObject_Str(d_key); + Py_DECREF(d_key); + return d_value; +} + +static PyObject *cdata_repr(CDataObject *cd) +{ + char *extra; + PyObject *result, *s; + + if (cd->c_type->ct_flags & CT_PRIMITIVE_ANY) { + if (cd->c_type->ct_flags & CT_IS_ENUM) { + s = convert_cdata_to_enum_string(cd, 1); + } + else if (cd->c_type->ct_flags & CT_IS_LONGDOUBLE) { + long double lvalue; + char buffer[128]; /* big enough */ + /*READ(cd->c_data, sizeof(long double)*/ + lvalue = read_raw_longdouble_data(cd->c_data); + sprintf(buffer, "%LE", lvalue); + s = PyText_FromString(buffer); + } + else { + PyObject *o = convert_to_object(cd->c_data, cd->c_type); + if (o == NULL) + return NULL; + s = PyObject_Repr(o); + Py_DECREF(o); + } + } + else if ((cd->c_type->ct_flags & CT_ARRAY) && cd->c_type->ct_length < 0) { + s = PyText_FromFormat("sliced length %zd", get_array_length(cd)); + } + else { + if (cd->c_data != NULL) { + s = PyText_FromFormat("%p", cd->c_data); + } + else + s = PyText_FromString("NULL"); + } + if (s == NULL) + return NULL; + /* it's slightly confusing to get "" because the + struct foo is not owned. Trying to make it clearer, write in this + case "". */ + if (cd->c_type->ct_flags & (CT_STRUCT|CT_UNION)) + extra = " &"; + else + extra = ""; + result = PyText_FromFormat("", + cd->c_type->ct_name, extra, + PyText_AsUTF8(s)); + Py_DECREF(s); + return result; +} + +static PyObject *_cdata_repr2(CDataObject *cd, char *text, PyObject *x) +{ + PyObject *res, *s = PyObject_Repr(x); + if (s == NULL) + return NULL; + res = PyText_FromFormat("", + cd->c_type->ct_name, text, PyText_AsUTF8(s)); + Py_DECREF(s); + return res; +} + +static Py_ssize_t _cdata_var_byte_size(CDataObject *cd) +{ + /* If 'cd' is a 'struct foo' or 'struct foo *' allocated with + ffi.new(), and if the struct foo contains a varsize array, + then return the real allocated size. Otherwise, return -1. */ + if (!CDataOwn_Check(cd)) + return -1; + + if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) { + cd = (CDataObject *)((CDataObject_own_structptr *)cd)->structobj; + } + if (cd->c_type->ct_flags & CT_WITH_VAR_ARRAY) { + return ((CDataObject_own_length *)cd)->length; + } + return -1; +} + +static PyObject *cdataowning_repr(CDataObject *cd) +{ + Py_ssize_t size = _cdata_var_byte_size(cd); + if (size < 0) { + if (cd->c_type->ct_flags & CT_POINTER) + size = cd->c_type->ct_itemdescr->ct_size; + else if (cd->c_type->ct_flags & CT_ARRAY) + size = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size; + else + size = cd->c_type->ct_size; + } + return PyText_FromFormat("", + cd->c_type->ct_name, size); +} + +static PyObject *cdataowninggc_repr(CDataObject *cd) +{ + if (cd->c_type->ct_flags & CT_IS_VOID_PTR) { /* a handle */ + PyObject *x = ((CDataObject_own_structptr *)cd)->structobj; + return _cdata_repr2(cd, "handle to", x); + } + else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) { /* a callback */ + ffi_closure *closure = ((CDataObject_closure *)cd)->closure; + PyObject *args = (PyObject *)closure->user_data; + if (args == NULL) + return cdata_repr(cd); + else + return _cdata_repr2(cd, "calling", PyTuple_GET_ITEM(args, 1)); + } + else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */ + Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview; + Py_ssize_t buflen = get_array_length(cd); + return PyText_FromFormat( + "", + cd->c_type->ct_name, + buflen, + view->obj ? Py_TYPE(view->obj)->tp_name : "(null)"); + } + return cdataowning_repr(cd); +} + +static int cdata_nonzero(CDataObject *cd) +{ + if (cd->c_type->ct_flags & CT_PRIMITIVE_ANY) { + if (cd->c_type->ct_flags & (CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED | + CT_PRIMITIVE_CHAR)) + return read_raw_unsigned_data(cd->c_data, cd->c_type->ct_size) != 0; + + if (cd->c_type->ct_flags & CT_PRIMITIVE_FLOAT) { + if (cd->c_type->ct_flags & CT_IS_LONGDOUBLE) + return read_raw_longdouble_data(cd->c_data) != 0.0; + return read_raw_float_data(cd->c_data, cd->c_type->ct_size) != 0.0; + } + if (cd->c_type->ct_flags & CT_PRIMITIVE_COMPLEX) { + Py_complex value = read_raw_complex_data(cd->c_data, + cd->c_type->ct_size); + return value.real != 0.0 || value.imag != 0.0; + } + } + return cd->c_data != NULL; +} + +static PyObject *cdata_int(CDataObject *cd) +{ + if ((cd->c_type->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_FITS_LONG)) + == (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_FITS_LONG)) { + /* this case is to handle enums, but also serves as a slight + performance improvement for some other primitive types */ + long value; + /*READ(cd->c_data, cd->c_type->ct_size)*/ + value = (long)read_raw_signed_data(cd->c_data, cd->c_type->ct_size); + return PyInt_FromLong(value); + } + if (cd->c_type->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_UNSIGNED)) { + return convert_to_object(cd->c_data, cd->c_type); + } + else if (cd->c_type->ct_flags & CT_PRIMITIVE_CHAR) { + /*READ(cd->c_data, cd->c_type->ct_size)*/ + switch (cd->c_type->ct_size) { + case sizeof(char): + return PyInt_FromLong((unsigned char)cd->c_data[0]); + case 2: + return PyInt_FromLong((long)*(cffi_char16_t *)cd->c_data); + case 4: + if (cd->c_type->ct_flags & CT_IS_SIGNED_WCHAR) + return PyInt_FromLong((long)*(int32_t *)cd->c_data); + else if (sizeof(long) > 4) + return PyInt_FromLong(*(uint32_t *)cd->c_data); + else + return PyLong_FromUnsignedLong(*(uint32_t *)cd->c_data); + } + } + else if (cd->c_type->ct_flags & CT_PRIMITIVE_FLOAT) { + PyObject *o = cdata_float(cd); +#if PY_MAJOR_VERSION < 3 + PyObject *r = o ? PyNumber_Int(o) : NULL; +#else + PyObject *r = o ? PyNumber_Long(o) : NULL; +#endif + Py_XDECREF(o); + return r; + } + PyErr_Format(PyExc_TypeError, "int() not supported on cdata '%s'", + cd->c_type->ct_name); + return NULL; +} + +#if PY_MAJOR_VERSION < 3 +static PyObject *cdata_long(CDataObject *cd) +{ + PyObject *res = cdata_int(cd); + if (res != NULL && PyInt_CheckExact(res)) { + PyObject *o = PyLong_FromLong(PyInt_AS_LONG(res)); + Py_DECREF(res); + res = o; + } + return res; +} +#endif + +static PyObject *cdata_float(CDataObject *cd) +{ + if (cd->c_type->ct_flags & CT_PRIMITIVE_FLOAT) { + double value; + /*READ(cd->c_data, cd->c_type->ct_size)*/ + if (!(cd->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + value = read_raw_float_data(cd->c_data, cd->c_type->ct_size); + } + else { + value = (double)read_raw_longdouble_data(cd->c_data); + } + return PyFloat_FromDouble(value); + } + PyErr_Format(PyExc_TypeError, "float() not supported on cdata '%s'", + cd->c_type->ct_name); + return NULL; +} + +static PyObject *cdata_richcompare(PyObject *v, PyObject *w, int op) +{ + int v_is_ptr, w_is_ptr; + PyObject *pyres; + + assert(CData_Check(v)); + + /* Comparisons involving a primitive cdata work differently than + * comparisons involving a struct/array/pointer. + * + * If v or w is a struct/array/pointer, then the other must be too + * (otherwise we return NotImplemented and leave the case to + * Python). If both are, then we compare the addresses. + * + * If v and/or w is a primitive cdata, then we convert the cdata(s) + * to regular Python objects and redo the comparison there. + */ + + v_is_ptr = !(((CDataObject *)v)->c_type->ct_flags & CT_PRIMITIVE_ANY); + w_is_ptr = CData_Check(w) && + !(((CDataObject *)w)->c_type->ct_flags & CT_PRIMITIVE_ANY); + + if (v_is_ptr && w_is_ptr) { + int res; + char *v_cdata = ((CDataObject *)v)->c_data; + char *w_cdata = ((CDataObject *)w)->c_data; + + switch (op) { + case Py_EQ: res = (v_cdata == w_cdata); break; + case Py_NE: res = (v_cdata != w_cdata); break; + case Py_LT: res = (v_cdata < w_cdata); break; + case Py_LE: res = (v_cdata <= w_cdata); break; + case Py_GT: res = (v_cdata > w_cdata); break; + case Py_GE: res = (v_cdata >= w_cdata); break; + default: res = -1; + } + pyres = res ? Py_True : Py_False; + } + else if (v_is_ptr || w_is_ptr) { + pyres = Py_NotImplemented; + } + else { + PyObject *aa[2]; + int i; + + aa[0] = v; Py_INCREF(v); + aa[1] = w; Py_INCREF(w); + pyres = NULL; + + for (i = 0; i < 2; i++) { + v = aa[i]; + if (!CData_Check(v)) + continue; + w = convert_to_object(((CDataObject *)v)->c_data, + ((CDataObject *)v)->c_type); + if (w == NULL) + goto error; + if (CData_Check(w)) { + Py_DECREF(w); + PyErr_Format(PyExc_NotImplementedError, + "cannot use in a comparison", + ((CDataObject *)v)->c_type->ct_name); + goto error; + } + aa[i] = w; + Py_DECREF(v); + } + pyres = PyObject_RichCompare(aa[0], aa[1], op); + error: + Py_DECREF(aa[1]); + Py_DECREF(aa[0]); + return pyres; + } + + Py_INCREF(pyres); + return pyres; +} + +static long cdata_hash(CDataObject *v) +{ + if (((CDataObject *)v)->c_type->ct_flags & CT_PRIMITIVE_ANY) { + PyObject *vv = convert_to_object(((CDataObject *)v)->c_data, + ((CDataObject *)v)->c_type); + if (vv == NULL) + return -1; + if (!CData_Check(vv)) { + long hash = PyObject_Hash(vv); + Py_DECREF(vv); + return hash; + } + Py_DECREF(vv); + } + return _Py_HashPointer(v->c_data); +} + +static Py_ssize_t +cdata_length(CDataObject *cd) +{ + if (cd->c_type->ct_flags & CT_ARRAY) { + return get_array_length(cd); + } + PyErr_Format(PyExc_TypeError, "cdata of type '%s' has no len()", + cd->c_type->ct_name); + return -1; +} + +static char * +_cdata_get_indexed_ptr(CDataObject *cd, PyObject *key) +{ + Py_ssize_t i = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return NULL; + + if (cd->c_type->ct_flags & CT_POINTER) { + if (CDataOwn_Check(cd)) { + if (i != 0) { + PyErr_Format(PyExc_IndexError, + "cdata '%s' can only be indexed by 0", + cd->c_type->ct_name); + return NULL; + } + } + else { + if (cd->c_data == NULL) { + PyErr_Format(PyExc_RuntimeError, + "cannot dereference null pointer from cdata '%s'", + cd->c_type->ct_name); + return NULL; + } + } + } + else if (cd->c_type->ct_flags & CT_ARRAY) { + if (i < 0) { + PyErr_SetString(PyExc_IndexError, + "negative index"); + return NULL; + } + if (i >= get_array_length(cd)) { + PyErr_Format(PyExc_IndexError, + "index too large for cdata '%s' (expected %zd < %zd)", + cd->c_type->ct_name, + i, get_array_length(cd)); + return NULL; + } + } + else { + PyErr_Format(PyExc_TypeError, "cdata of type '%s' cannot be indexed", + cd->c_type->ct_name); + return NULL; + } + return cd->c_data + i * cd->c_type->ct_itemdescr->ct_size; +} + +static PyObject * +new_array_type(CTypeDescrObject *ctptr, Py_ssize_t length); /* forward */ + +static CTypeDescrObject * +_cdata_getslicearg(CDataObject *cd, PySliceObject *slice, Py_ssize_t bounds[]) +{ + Py_ssize_t start, stop; + CTypeDescrObject *ct; + + start = PyInt_AsSsize_t(slice->start); + if (start == -1 && PyErr_Occurred()) { + if (slice->start == Py_None) + PyErr_SetString(PyExc_IndexError, "slice start must be specified"); + return NULL; + } + stop = PyInt_AsSsize_t(slice->stop); + if (stop == -1 && PyErr_Occurred()) { + if (slice->stop == Py_None) + PyErr_SetString(PyExc_IndexError, "slice stop must be specified"); + return NULL; + } + if (slice->step != Py_None) { + PyErr_SetString(PyExc_IndexError, "slice with step not supported"); + return NULL; + } + if (start > stop) { + PyErr_SetString(PyExc_IndexError, "slice start > stop"); + return NULL; + } + + ct = cd->c_type; + if (ct->ct_flags & CT_ARRAY) { + if (start < 0) { + PyErr_SetString(PyExc_IndexError, + "negative index"); + return NULL; + } + if (stop > get_array_length(cd)) { + PyErr_Format(PyExc_IndexError, + "index too large (expected %zd <= %zd)", + stop, get_array_length(cd)); + return NULL; + } + ct = (CTypeDescrObject *)ct->ct_stuff; + } + else if (!(ct->ct_flags & CT_POINTER)) { + PyErr_Format(PyExc_TypeError, "cdata of type '%s' cannot be indexed", + ct->ct_name); + return NULL; + } + + bounds[0] = start; + bounds[1] = stop - start; + return ct; +} + +static PyObject * +cdata_slice(CDataObject *cd, PySliceObject *slice) +{ + char *cdata; + Py_ssize_t bounds[2]; + CTypeDescrObject *ct = _cdata_getslicearg(cd, slice, bounds); + if (ct == NULL) + return NULL; + + if (ct->ct_stuff == NULL) { + ct->ct_stuff = new_array_type(ct, -1); + if (ct->ct_stuff == NULL) + return NULL; + } + ct = (CTypeDescrObject *)ct->ct_stuff; + + cdata = cd->c_data + ct->ct_itemdescr->ct_size * bounds[0]; + return new_sized_cdata(cdata, ct, bounds[1]); +} + +static int +cdata_ass_slice(CDataObject *cd, PySliceObject *slice, PyObject *v) +{ + Py_ssize_t bounds[2], i, length, itemsize; + PyObject *it, *item; + PyObject *(*iternext)(PyObject *); + char *cdata; + int err; + CTypeDescrObject *ct = _cdata_getslicearg(cd, slice, bounds); + if (ct == NULL) + return -1; + ct = ct->ct_itemdescr; + itemsize = ct->ct_size; + cdata = cd->c_data + itemsize * bounds[0]; + length = bounds[1]; + + if (CData_Check(v)) { + CTypeDescrObject *ctv = ((CDataObject *)v)->c_type; + if ((ctv->ct_flags & CT_ARRAY) && (ctv->ct_itemdescr == ct) && + (get_array_length((CDataObject *)v) == length)) { + /* fast path: copying from exactly the correct type */ + memmove(cdata, ((CDataObject *)v)->c_data, itemsize * length); + return 0; + } + } + + /* A fast path for [0:N] = b"somestring" or bytearray, which + also adds support for Python 3: otherwise, you get integers while + enumerating the string, and you can't set them to characters :-/ + */ + if ((ct->ct_flags & CT_PRIMITIVE_CHAR) && itemsize == sizeof(char)) { + char *src; + Py_ssize_t srclen; + if (PyBytes_Check(v)) { + srclen = PyBytes_GET_SIZE(v); + src = PyBytes_AS_STRING(v); + } + else if (PyByteArray_Check(v)) { + srclen = PyByteArray_GET_SIZE(v); + src = PyByteArray_AS_STRING(v); + } + else + goto other_types; + + if (srclen != length) { + PyErr_Format(PyExc_ValueError, + "need a string of length %zd, got %zd", + length, srclen); + return -1; + } + memcpy(cdata, src, length); + return 0; + } + other_types: + + it = PyObject_GetIter(v); + if (it == NULL) + return -1; + iternext = *it->ob_type->tp_iternext; + + for (i = 0; i < length; i++) { + item = iternext(it); + if (item == NULL) { + if (!PyErr_Occurred()) + PyErr_Format(PyExc_ValueError, + "need %zd values to unpack, got %zd", + length, i); + goto error; + } + err = convert_from_object(cdata, ct, item); + Py_DECREF(item); + if (err < 0) + goto error; + + cdata += itemsize; + } + item = iternext(it); + if (item != NULL) { + Py_DECREF(item); + PyErr_Format(PyExc_ValueError, + "got more than %zd values to unpack", length); + } + error: + Py_DECREF(it); + return PyErr_Occurred() ? -1 : 0; +} + +static PyObject * +cdataowning_subscript(CDataObject *cd, PyObject *key) +{ + char *c; + if (PySlice_Check(key)) + return cdata_slice(cd, (PySliceObject *)key); + + c = _cdata_get_indexed_ptr(cd, key); + /* use 'mp_subscript' instead of 'sq_item' because we don't want + negative indexes to be corrected automatically */ + if (c == NULL && PyErr_Occurred()) + return NULL; + + if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) { + PyObject *res = ((CDataObject_own_structptr *)cd)->structobj; + Py_INCREF(res); + return res; + } + else { + return convert_to_object(c, cd->c_type->ct_itemdescr); + } +} + +static PyObject * +cdata_subscript(CDataObject *cd, PyObject *key) +{ + char *c; + if (PySlice_Check(key)) + return cdata_slice(cd, (PySliceObject *)key); + + c = _cdata_get_indexed_ptr(cd, key); + /* use 'mp_subscript' instead of 'sq_item' because we don't want + negative indexes to be corrected automatically */ + if (c == NULL && PyErr_Occurred()) + return NULL; + return convert_to_object(c, cd->c_type->ct_itemdescr); +} + +static int +cdata_ass_sub(CDataObject *cd, PyObject *key, PyObject *v) +{ + char *c; + CTypeDescrObject *ctitem; + if (PySlice_Check(key)) + return cdata_ass_slice(cd, (PySliceObject *)key, v); + + c = _cdata_get_indexed_ptr(cd, key); + ctitem = cd->c_type->ct_itemdescr; + /* use 'mp_ass_subscript' instead of 'sq_ass_item' because we don't want + negative indexes to be corrected automatically */ + if (c == NULL && PyErr_Occurred()) + return -1; + if (v == NULL) { + PyErr_SetString(PyExc_TypeError, + "'del x[n]' not supported for cdata objects"); + return -1; + } + return convert_from_object(c, ctitem, v); +} + +static PyObject * +_cdata_add_or_sub(PyObject *v, PyObject *w, int sign) +{ + Py_ssize_t i, itemsize; + CDataObject *cd; + CTypeDescrObject *ctptr; + + if (!CData_Check(v)) { + PyObject *swap; + assert(CData_Check(w)); + if (sign != 1) + goto not_implemented; + swap = v; + v = w; + w = swap; + } + + i = PyNumber_AsSsize_t(w, PyExc_OverflowError); + if (i == -1 && PyErr_Occurred()) + return NULL; + i *= sign; + + cd = (CDataObject *)v; + if (cd->c_type->ct_flags & CT_POINTER) + ctptr = cd->c_type; + else if (cd->c_type->ct_flags & CT_ARRAY) { + ctptr = (CTypeDescrObject *)cd->c_type->ct_stuff; + } + else { + PyErr_Format(PyExc_TypeError, "cannot add a cdata '%s' and a number", + cd->c_type->ct_name); + return NULL; + } + itemsize = ctptr->ct_itemdescr->ct_size; + if (itemsize < 0) { + if (ctptr->ct_flags & CT_IS_VOID_PTR) { + itemsize = 1; + } + else { + PyErr_Format(PyExc_TypeError, + "ctype '%s' points to items of unknown size", + cd->c_type->ct_name); + return NULL; + } + } + return new_simple_cdata(cd->c_data + i * itemsize, ctptr); + + not_implemented: + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyObject * +cdata_add(PyObject *v, PyObject *w) +{ + return _cdata_add_or_sub(v, w, +1); +} + +static PyObject * +cdata_sub(PyObject *v, PyObject *w) +{ + if (CData_Check(v) && CData_Check(w)) { + CDataObject *cdv = (CDataObject *)v; + CDataObject *cdw = (CDataObject *)w; + CTypeDescrObject *ct = cdw->c_type; + Py_ssize_t diff, itemsize; + + if (ct->ct_flags & CT_ARRAY) /* ptr_to_T - array_of_T: ok */ + ct = (CTypeDescrObject *)ct->ct_stuff; + + if (ct != cdv->c_type || !(ct->ct_flags & CT_POINTER) || + (ct->ct_itemdescr->ct_size <= 0 && + !(ct->ct_flags & CT_IS_VOID_PTR))) { + PyErr_Format(PyExc_TypeError, + "cannot subtract cdata '%s' and cdata '%s'", + cdv->c_type->ct_name, ct->ct_name); + return NULL; + } + itemsize = ct->ct_itemdescr->ct_size; + diff = cdv->c_data - cdw->c_data; + if (itemsize > 1) { + if (diff % itemsize) { + PyErr_SetString(PyExc_ValueError, + "pointer subtraction: the distance between the two " + "pointers is not a multiple of the item size"); + return NULL; + } + diff = diff / itemsize; + } +#if PY_MAJOR_VERSION < 3 + return PyInt_FromSsize_t(diff); +#else + return PyLong_FromSsize_t(diff); +#endif + } + + return _cdata_add_or_sub(v, w, -1); +} + +static void +_cdata_attr_errmsg(char *errmsg, CDataObject *cd, PyObject *attr) +{ + const char *text; + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + return; + PyErr_Clear(); + text = PyText_AsUTF8(attr); + if (text == NULL) + return; + PyErr_Format(PyExc_AttributeError, errmsg, cd->c_type->ct_name, text); +} + +static PyObject * +cdata_getattro(CDataObject *cd, PyObject *attr) +{ + CFieldObject *cf; + CTypeDescrObject *ct = cd->c_type; + char *errmsg = "cdata '%s' has no attribute '%s'"; + PyObject *x; + + if (ct->ct_flags & CT_POINTER) + ct = ct->ct_itemdescr; + + if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { + switch (force_lazy_struct(ct)) { + case 1: + cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, attr); + if (cf != NULL) { + /* read the field 'cf' */ + char *data = cd->c_data + cf->cf_offset; + Py_ssize_t array_len, size; + + if (cf->cf_bitshift == BS_REGULAR) { + return convert_to_object(data, cf->cf_type); + } + else if (cf->cf_bitshift != BS_EMPTY_ARRAY) { + return convert_to_object_bitfield(data, cf); + } + + /* variable-length array: */ + /* if reading variable length array from variable length + struct, calculate array type from allocated length */ + size = _cdata_var_byte_size(cd) - cf->cf_offset; + if (size >= 0) { + array_len = size / cf->cf_type->ct_itemdescr->ct_size; + return new_sized_cdata(data, cf->cf_type, array_len); + } + return new_simple_cdata(data, + (CTypeDescrObject *)cf->cf_type->ct_stuff); + } + errmsg = "cdata '%s' has no field '%s'"; + break; + case -1: + return NULL; + default: + errmsg = "cdata '%s' points to an opaque type: cannot read fields"; + break; + } + } + x = PyObject_GenericGetAttr((PyObject *)cd, attr); + if (x == NULL) + _cdata_attr_errmsg(errmsg, cd, attr); + return x; +} + +static int +cdata_setattro(CDataObject *cd, PyObject *attr, PyObject *value) +{ + CFieldObject *cf; + CTypeDescrObject *ct = cd->c_type; + char *errmsg = "cdata '%s' has no attribute '%s'"; + int x; + + if (ct->ct_flags & CT_POINTER) + ct = ct->ct_itemdescr; + + if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { + switch (force_lazy_struct(ct)) { + case 1: + cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, attr); + if (cf != NULL) { + /* write the field 'cf' */ + if (value != NULL) { + return convert_field_from_object(cd->c_data, cf, value); + } + else { + PyErr_SetString(PyExc_AttributeError, + "cannot delete struct field"); + return -1; + } + } + errmsg = "cdata '%s' has no field '%s'"; + break; + case -1: + return -1; + default: + errmsg = "cdata '%s' points to an opaque type: cannot write fields"; + break; + } + } + x = PyObject_GenericSetAttr((PyObject *)cd, attr, value); + if (x < 0) + _cdata_attr_errmsg(errmsg, cd, attr); + return x; +} + +static PyObject * +convert_struct_to_owning_object(char *data, CTypeDescrObject *ct); /*forward*/ + +static cif_description_t * +fb_prepare_cif(PyObject *fargs, CTypeDescrObject *, ffi_abi); /*forward*/ + +static PyObject *new_primitive_type(const char *name); /*forward*/ + +static CTypeDescrObject *_get_ct_int(void) +{ + static CTypeDescrObject *ct_int = NULL; + if (ct_int == NULL) { + ct_int = (CTypeDescrObject *)new_primitive_type("int"); + } + return ct_int; +} + +static Py_ssize_t +_prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init, + char **output_data) +{ + /* 'ctptr' is here a pointer type 'ITEM *'. Accept as argument an + initializer for an array 'ITEM[]'. This includes the case of + passing a Python byte string to a 'char *' argument. + + This function returns -1 if an error occurred, + 0 if conversion succeeded (into *output_data), + or N > 0 if conversion would require N bytes of storage. + */ + Py_ssize_t length, datasize; + CTypeDescrObject *ctitem; + + if (CData_Check(init)) + goto convert_default; + + ctitem = ctptr->ct_itemdescr; + /* XXX some code duplication, how to avoid it? */ + if (PyBytes_Check(init)) { + /* from a string: just returning the string here is fine. + We assume that the C code won't modify the 'char *' data. */ + if ((ctptr->ct_flags & CT_IS_VOIDCHAR_PTR) || + ((ctitem->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_UNSIGNED)) + && (ctitem->ct_size == sizeof(char)))) { +#if defined(CFFI_MEM_DEBUG) || defined(CFFI_MEM_LEAK) + length = PyBytes_GET_SIZE(init) + 1; +#else + *output_data = PyBytes_AS_STRING(init); + if (ctitem->ct_flags & CT_IS_BOOL) + if (must_be_array_of_zero_or_one(*output_data, + PyBytes_GET_SIZE(init)) < 0) + return -1; + return 0; +#endif + } + else + goto convert_default; + } + else if (PyList_Check(init) || PyTuple_Check(init)) { + length = PySequence_Fast_GET_SIZE(init); + } + else if (PyUnicode_Check(init)) { + /* from a unicode, we add the null terminator */ + if (ctitem->ct_size == 2) + length = _my_PyUnicode_SizeAsChar16(init); + else + length = _my_PyUnicode_SizeAsChar32(init); + length += 1; + } + else if ((ctitem->ct_flags & CT_IS_FILE) && PyFile_Check(init)) { + *output_data = (char *)PyFile_AsFile(init); + if (*output_data == NULL && PyErr_Occurred()) + return -1; + return 0; + } + else { + /* refuse to receive just an integer (and interpret it + as the array size) */ + goto convert_default; + } + + if (ctitem->ct_size <= 0) + goto convert_default; + datasize = MUL_WRAPAROUND(length, ctitem->ct_size); + if ((datasize / ctitem->ct_size) != length) { + PyErr_SetString(PyExc_OverflowError, + "array size would overflow a Py_ssize_t"); + return -1; + } + if (datasize <= 0) + datasize = 1; + return datasize; + + convert_default: + return convert_from_object((char *)output_data, ctptr, init); +} + +static PyObject* +cdata_call(CDataObject *cd, PyObject *args, PyObject *kwds) +{ + char *buffer; + void** buffer_array; + cif_description_t *cif_descr; + Py_ssize_t i, nargs, nargs_declared; + PyObject *signature, *res = NULL, *fvarargs; + CTypeDescrObject *fresult; + char *resultdata; + char *errormsg; + + if (!(cd->c_type->ct_flags & CT_FUNCTIONPTR)) { + PyErr_Format(PyExc_TypeError, "cdata '%s' is not callable", + cd->c_type->ct_name); + return NULL; + } + if (kwds != NULL && PyDict_Size(kwds) != 0) { + PyErr_SetString(PyExc_TypeError, + "a cdata function cannot be called with keyword arguments"); + return NULL; + } + signature = cd->c_type->ct_stuff; + nargs = PyTuple_Size(args); + if (nargs < 0) + return NULL; + nargs_declared = PyTuple_GET_SIZE(signature) - 2; + fresult = (CTypeDescrObject *)PyTuple_GET_ITEM(signature, 1); + fvarargs = NULL; + buffer = NULL; + + cif_descr = (cif_description_t *)cd->c_type->ct_extra; + + if (cif_descr != NULL) { + /* regular case: this function does not take '...' arguments */ + if (nargs != nargs_declared) { + errormsg = "'%s' expects %zd arguments, got %zd"; + bad_number_of_arguments: + PyErr_Format(PyExc_TypeError, errormsg, + cd->c_type->ct_name, nargs_declared, nargs); + goto error; + } + } + else { + /* call of a variadic function */ + ffi_abi fabi; + if (nargs < nargs_declared) { + errormsg = "'%s' expects at least %zd arguments, got %zd"; + goto bad_number_of_arguments; + } + fvarargs = PyTuple_New(nargs); + if (fvarargs == NULL) + goto error; + for (i = 0; i < nargs_declared; i++) { + PyObject *o = PyTuple_GET_ITEM(signature, 2 + i); + Py_INCREF(o); + PyTuple_SET_ITEM(fvarargs, i, o); + } + for (i = nargs_declared; i < nargs; i++) { + PyObject *obj = PyTuple_GET_ITEM(args, i); + CTypeDescrObject *ct; + + if (CData_Check(obj)) { + ct = ((CDataObject *)obj)->c_type; + if (ct->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_UNSIGNED | + CT_PRIMITIVE_SIGNED)) { + if (ct->ct_size < (Py_ssize_t)sizeof(int)) { + ct = _get_ct_int(); + if (ct == NULL) + goto error; + } + } + else if (ct->ct_flags & CT_ARRAY) { + ct = (CTypeDescrObject *)ct->ct_stuff; + } + Py_INCREF(ct); + } + else { + PyErr_Format(PyExc_TypeError, + "argument %zd passed in the variadic part " + "needs to be a cdata object (got %.200s)", + i + 1, Py_TYPE(obj)->tp_name); + goto error; + } + PyTuple_SET_ITEM(fvarargs, i, (PyObject *)ct); + } +#if PY_MAJOR_VERSION < 3 + fabi = PyInt_AS_LONG(PyTuple_GET_ITEM(signature, 0)); +#else + fabi = PyLong_AS_LONG(PyTuple_GET_ITEM(signature, 0)); +#endif + cif_descr = fb_prepare_cif(fvarargs, fresult, fabi); + if (cif_descr == NULL) + goto error; + } + + buffer = PyObject_Malloc(cif_descr->exchange_size); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + + buffer_array = (void **)buffer; + + for (i=0; iexchange_offset_arg[1 + i]; + PyObject *obj = PyTuple_GET_ITEM(args, i); + + buffer_array[i] = data; + + if (i < nargs_declared) + argtype = (CTypeDescrObject *)PyTuple_GET_ITEM(signature, 2 + i); + else + argtype = (CTypeDescrObject *)PyTuple_GET_ITEM(fvarargs, i); + + if (argtype->ct_flags & CT_POINTER) { + char *tmpbuf; + Py_ssize_t datasize = _prepare_pointer_call_argument( + argtype, obj, (char **)data); + if (datasize == 0) + ; /* successfully filled '*data' */ + else if (datasize < 0) + goto error; + else { + tmpbuf = alloca(datasize); + memset(tmpbuf, 0, datasize); + *(char **)data = tmpbuf; + if (convert_array_from_object(tmpbuf, argtype, obj) < 0) + goto error; + } + } + else if (convert_from_object(data, argtype, obj) < 0) + goto error; + } + + resultdata = buffer + cif_descr->exchange_offset_arg[0]; + /*READ(cd->c_data, sizeof(void(*)(void)))*/ + + Py_BEGIN_ALLOW_THREADS + restore_errno(); + ffi_call(&cif_descr->cif, (void (*)(void))(cd->c_data), + resultdata, buffer_array); + save_errno(); + Py_END_ALLOW_THREADS + + if (fresult->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED)) { +#ifdef WORDS_BIGENDIAN + /* For results of precisely these types, libffi has a strange + rule that they will be returned as a whole 'ffi_arg' if they + are smaller. The difference only matters on big-endian. */ + if (fresult->ct_size < sizeof(ffi_arg)) + resultdata += (sizeof(ffi_arg) - fresult->ct_size); +#endif + res = convert_to_object(resultdata, fresult); + } + else if (fresult->ct_flags & CT_VOID) { + res = Py_None; + Py_INCREF(res); + } + else if (fresult->ct_flags & CT_STRUCT) { + res = convert_struct_to_owning_object(resultdata, fresult); + } + else { + res = convert_to_object(resultdata, fresult); + } + /* fall-through */ + + error: + if (buffer) + PyObject_Free(buffer); + if (fvarargs != NULL) { + Py_DECREF(fvarargs); + if (cif_descr != NULL) /* but only if fvarargs != NULL, if variadic */ + PyObject_Free(cif_descr); + } + return res; +} + +static PyObject *cdata_dir(PyObject *cd, PyObject *noarg) +{ + CTypeDescrObject *ct = ((CDataObject *)cd)->c_type; + + /* replace the type 'pointer-to-t' with just 't' */ + if (ct->ct_flags & CT_POINTER) { + ct = ct->ct_itemdescr; + } + if ((ct->ct_flags & (CT_STRUCT | CT_UNION)) && + !(ct->ct_flags & CT_IS_OPAQUE)) { + + /* for non-opaque structs or unions */ + if (force_lazy_struct(ct) < 0) + return NULL; + return PyDict_Keys(ct->ct_stuff); + } + else { + return PyList_New(0); /* empty list for the other cases */ + } +} + +static PyObject *cdata_complex(PyObject *cd_, PyObject *noarg) +{ + CDataObject *cd = (CDataObject *)cd_; + + if (cd->c_type->ct_flags & CT_PRIMITIVE_COMPLEX) { + Py_complex value = read_raw_complex_data(cd->c_data, cd->c_type->ct_size); + PyObject *op = PyComplex_FromCComplex(value); + return op; + } + /* or cannot be directly converted by + calling complex(), just like cannot be directly + converted by calling float() */ + + PyErr_Format(PyExc_TypeError, "complex() not supported on cdata '%s'", + cd->c_type->ct_name); + return NULL; +} + +static int explicit_release_case(PyObject *cd) +{ + CTypeDescrObject *ct = ((CDataObject *)cd)->c_type; + if (Py_TYPE(cd) == &CDataOwning_Type) { + if ((ct->ct_flags & (CT_POINTER | CT_ARRAY)) != 0) /* ffi.new() */ + return 0; + } + else if (Py_TYPE(cd) == &CDataOwningGC_Type) { + if (ct->ct_flags & CT_ARRAY) /* ffi.from_buffer() */ + return 1; + } + else if (Py_TYPE(cd) == &CDataGCP_Type) { + return 2; /* ffi.gc() */ + } + PyErr_SetString(PyExc_ValueError, + "only 'cdata' object from ffi.new(), ffi.gc(), ffi.from_buffer() " + "or ffi.new_allocator()() can be used with the 'with' keyword or " + "ffi.release()"); + return -1; +} + +static PyObject *cdata_enter(PyObject *cd, PyObject *noarg) +{ + if (explicit_release_case(cd) < 0) /* only to check the ctype */ + return NULL; + Py_INCREF(cd); + return cd; +} + +static PyObject *cdata_exit(PyObject *cd, PyObject *args) +{ + /* 'args' ignored */ + CTypeDescrObject *ct; + Py_buffer *view; + switch (explicit_release_case(cd)) + { + case 0: /* ffi.new() */ + /* no effect on CPython: raw memory is allocated with the + same malloc() as the object itself, so it can't be + released independently. If we use a custom allocator, + then it's implemented with ffi.gc(). */ + ct = ((CDataObject *)cd)->c_type; + if (ct->ct_flags & CT_IS_PTR_TO_OWNED) { + PyObject *x = ((CDataObject_own_structptr *)cd)->structobj; + if (Py_TYPE(x) == &CDataGCP_Type) { + /* this is a special case for + ffi.new_allocator()("struct-or-union") */ + cdatagcp_finalize((CDataObject_gcp *)x); + } + } + break; + + case 1: /* ffi.from_buffer() */ + view = ((CDataObject_owngc_frombuf *)cd)->bufferview; + PyBuffer_Release(view); + break; + + case 2: /* ffi.gc() or ffi.new_allocator()("not-struct-nor-union") */ + /* call the destructor immediately */ + cdatagcp_finalize((CDataObject_gcp *)cd); + break; + + default: + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cdata_iter(CDataObject *); + +static PyNumberMethods CData_as_number = { + (binaryfunc)cdata_add, /*nb_add*/ + (binaryfunc)cdata_sub, /*nb_subtract*/ + 0, /*nb_multiply*/ +#if PY_MAJOR_VERSION < 3 + 0, /*nb_divide*/ +#endif + 0, /*nb_remainder*/ + 0, /*nb_divmod*/ + 0, /*nb_power*/ + 0, /*nb_negative*/ + 0, /*nb_positive*/ + 0, /*nb_absolute*/ + (inquiry)cdata_nonzero, /*nb_nonzero*/ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + 0, /*nb_and*/ + 0, /*nb_xor*/ + 0, /*nb_or*/ +#if PY_MAJOR_VERSION < 3 + 0, /*nb_coerce*/ +#endif + (unaryfunc)cdata_int, /*nb_int*/ +#if PY_MAJOR_VERSION < 3 + (unaryfunc)cdata_long, /*nb_long*/ +#else + 0, +#endif + (unaryfunc)cdata_float, /*nb_float*/ + 0, /*nb_oct*/ + 0, /*nb_hex*/ +}; + +static PyMappingMethods CData_as_mapping = { + (lenfunc)cdata_length, /*mp_length*/ + (binaryfunc)cdata_subscript, /*mp_subscript*/ + (objobjargproc)cdata_ass_sub, /*mp_ass_subscript*/ +}; + +static PyMappingMethods CDataOwn_as_mapping = { + (lenfunc)cdata_length, /*mp_length*/ + (binaryfunc)cdataowning_subscript, /*mp_subscript*/ + (objobjargproc)cdata_ass_sub, /*mp_ass_subscript*/ +}; + +static PyMethodDef cdata_methods[] = { + {"__dir__", cdata_dir, METH_NOARGS}, + {"__complex__", cdata_complex, METH_NOARGS}, + {"__enter__", cdata_enter, METH_NOARGS}, + {"__exit__", cdata_exit, METH_VARARGS}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject CData_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CData", + sizeof(CDataObject), + 0, + (destructor)cdata_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)cdata_repr, /* tp_repr */ + &CData_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + &CData_as_mapping, /* tp_as_mapping */ + (hashfunc)cdata_hash, /* tp_hash */ + (ternaryfunc)cdata_call, /* tp_call */ + 0, /* tp_str */ + (getattrofunc)cdata_getattro, /* tp_getattro */ + (setattrofunc)cdata_setattro, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + cdata_richcompare, /* tp_richcompare */ + offsetof(CDataObject, c_weakreflist), /* tp_weaklistoffset */ + (getiterfunc)cdata_iter, /* tp_iter */ + 0, /* tp_iternext */ + cdata_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + PyObject_Del, /* tp_free */ +}; + +static PyTypeObject CDataOwning_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CDataOwn", + sizeof(CDataObject), + 0, + (destructor)cdataowning_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)cdataowning_repr, /* tp_repr */ + 0, /* inherited */ /* tp_as_number */ + 0, /* tp_as_sequence */ + &CDataOwn_as_mapping, /* tp_as_mapping */ + 0, /* inherited */ /* tp_hash */ + 0, /* inherited */ /* tp_call */ + 0, /* tp_str */ + 0, /* inherited */ /* tp_getattro */ + 0, /* inherited */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* inherited */ /* tp_richcompare */ + 0, /* inherited */ /* tp_weaklistoffset */ + 0, /* inherited */ /* tp_iter */ + 0, /* tp_iternext */ + 0, /* inherited */ /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &CData_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + free, /* tp_free */ +}; + +static PyTypeObject CDataOwningGC_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CDataOwnGC", + sizeof(CDataObject_owngc_frombuf), + 0, + (destructor)cdataowninggc_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)cdataowninggc_repr, /* tp_repr */ + 0, /* inherited */ /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* inherited */ /* tp_as_mapping */ + 0, /* inherited */ /* tp_hash */ + 0, /* inherited */ /* tp_call */ + 0, /* tp_str */ + 0, /* inherited */ /* tp_getattro */ + 0, /* inherited */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES /* tp_flags */ + | Py_TPFLAGS_HAVE_GC, + 0, /* tp_doc */ + (traverseproc)cdataowninggc_traverse, /* tp_traverse */ + (inquiry)cdataowninggc_clear, /* tp_clear */ + 0, /* inherited */ /* tp_richcompare */ + 0, /* inherited */ /* tp_weaklistoffset */ + 0, /* inherited */ /* tp_iter */ + 0, /* tp_iternext */ + 0, /* inherited */ /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &CDataOwning_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + +static PyTypeObject CDataGCP_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CDataGCP", + sizeof(CDataObject_gcp), + 0, + (destructor)cdatagcp_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* inherited */ /* tp_repr */ + 0, /* inherited */ /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* inherited */ /* tp_as_mapping */ + 0, /* inherited */ /* tp_hash */ + 0, /* inherited */ /* tp_call */ + 0, /* tp_str */ + 0, /* inherited */ /* tp_getattro */ + 0, /* inherited */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES /* tp_flags */ +#ifdef Py_TPFLAGS_HAVE_FINALIZE + | Py_TPFLAGS_HAVE_FINALIZE +#endif + | Py_TPFLAGS_HAVE_GC, + 0, /* tp_doc */ + (traverseproc)cdatagcp_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* inherited */ /* tp_richcompare */ + 0, /* inherited */ /* tp_weaklistoffset */ + 0, /* inherited */ /* tp_iter */ + 0, /* tp_iternext */ + 0, /* inherited */ /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &CData_Type, /* tp_base */ +#ifdef Py_TPFLAGS_HAVE_FINALIZE /* CPython >= 3.4 */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0, /* inherited */ /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ + 0, /* tp_del */ + 0, /* version_tag */ + (destructor)cdatagcp_finalize, /* tp_finalize */ +#endif +}; + +/************************************************************/ + +typedef struct { + PyObject_HEAD + char *di_next, *di_stop; + CDataObject *di_object; + CTypeDescrObject *di_itemtype; +} CDataIterObject; + +static PyObject * +cdataiter_next(CDataIterObject *it) +{ + char *result = it->di_next; + if (result != it->di_stop) { + it->di_next = result + it->di_itemtype->ct_size; + return convert_to_object(result, it->di_itemtype); + } + return NULL; +} + +static void +cdataiter_dealloc(CDataIterObject *it) +{ + Py_DECREF(it->di_object); + PyObject_Del(it); +} + +static PyTypeObject CDataIter_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.CDataIter", /* tp_name */ + sizeof(CDataIterObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)cdataiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)cdataiter_next, /* tp_iternext */ +}; + +static PyObject * +cdata_iter(CDataObject *cd) +{ + CDataIterObject *it; + + if (!(cd->c_type->ct_flags & CT_ARRAY)) { + PyErr_Format(PyExc_TypeError, "cdata '%s' does not support iteration", + cd->c_type->ct_name); + return NULL; + } + + it = PyObject_New(CDataIterObject, &CDataIter_Type); + if (it == NULL) + return NULL; + + Py_INCREF(cd); + it->di_object = cd; + it->di_itemtype = cd->c_type->ct_itemdescr; + it->di_next = cd->c_data; + it->di_stop = cd->c_data + get_array_length(cd) * it->di_itemtype->ct_size; + return (PyObject *)it; +} + +/************************************************************/ + +static CDataObject *allocate_owning_object(Py_ssize_t size, + CTypeDescrObject *ct, + int dont_clear) +{ + /* note: objects with &CDataOwning_Type are always allocated with + either a plain malloc() or calloc(), and freed with free(). */ + CDataObject *cd; + if (dont_clear) + cd = malloc(size); + else + cd = calloc(size, 1); + if (PyObject_Init((PyObject *)cd, &CDataOwning_Type) == NULL) + return NULL; + + Py_INCREF(ct); + cd->c_type = ct; + cd->c_weakreflist = NULL; + return cd; +} + +static PyObject * +convert_struct_to_owning_object(char *data, CTypeDescrObject *ct) +{ + /* also accepts unions, for the API mode */ + CDataObject *cd; + Py_ssize_t dataoffset = offsetof(CDataObject_own_nolength, alignment); + Py_ssize_t datasize = ct->ct_size; + + if (datasize < 0) { + PyErr_SetString(PyExc_TypeError, + "return type is an opaque structure or union"); + return NULL; + } + if (ct->ct_flags & CT_WITH_VAR_ARRAY) { + PyErr_SetString(PyExc_TypeError, + "return type is a struct/union with a varsize array member"); + return NULL; + } + cd = allocate_owning_object(dataoffset + datasize, ct, /*dont_clear=*/1); + if (cd == NULL) + return NULL; + cd->c_data = ((char *)cd) + dataoffset; + + memcpy(cd->c_data, data, datasize); + return (PyObject *)cd; +} + +static CDataObject *allocate_gcp_object(CDataObject *origobj, + CTypeDescrObject *ct, + PyObject *destructor) +{ + CDataObject_gcp *cd = PyObject_GC_New(CDataObject_gcp, &CDataGCP_Type); + if (cd == NULL) + return NULL; + + Py_XINCREF(destructor); + Py_INCREF(origobj); + Py_INCREF(ct); + cd->head.c_data = origobj->c_data; + cd->head.c_type = ct; + cd->head.c_weakreflist = NULL; + cd->origobj = (PyObject *)origobj; + cd->destructor = destructor; + + PyObject_GC_Track(cd); + return (CDataObject *)cd; +} + +static CDataObject *allocate_with_allocator(Py_ssize_t basesize, + Py_ssize_t datasize, + CTypeDescrObject *ct, + const cffi_allocator_t *allocator) +{ + CDataObject *cd; + + if (allocator->ca_alloc == NULL) { + cd = allocate_owning_object(basesize + datasize, ct, + allocator->ca_dont_clear); + if (cd == NULL) + return NULL; + cd->c_data = ((char *)cd) + basesize; + } + else { + PyObject *res = PyObject_CallFunction(allocator->ca_alloc, "n", datasize); + if (res == NULL) + return NULL; + + if (!CData_Check(res)) { + PyErr_Format(PyExc_TypeError, + "alloc() must return a cdata object (got %.200s)", + Py_TYPE(res)->tp_name); + Py_DECREF(res); + return NULL; + } + cd = (CDataObject *)res; + if (!(cd->c_type->ct_flags & (CT_POINTER|CT_ARRAY))) { + PyErr_Format(PyExc_TypeError, + "alloc() must return a cdata pointer, not '%s'", + cd->c_type->ct_name); + Py_DECREF(res); + return NULL; + } + if (!cd->c_data) { + PyErr_SetString(PyExc_MemoryError, "alloc() returned NULL"); + Py_DECREF(res); + return NULL; + } + + cd = allocate_gcp_object(cd, ct, allocator->ca_free); + Py_DECREF(res); + if (!allocator->ca_dont_clear) + memset(cd->c_data, 0, datasize); + } + return cd; +} + +static PyObject *direct_newp(CTypeDescrObject *ct, PyObject *init, + const cffi_allocator_t *allocator) +{ + CTypeDescrObject *ctitem; + CDataObject *cd; + Py_ssize_t dataoffset, datasize, explicitlength; + + explicitlength = -1; + if (ct->ct_flags & CT_POINTER) { + dataoffset = offsetof(CDataObject_own_nolength, alignment); + ctitem = ct->ct_itemdescr; + datasize = ctitem->ct_size; + if (datasize < 0) { + PyErr_Format(PyExc_TypeError, + "cannot instantiate ctype '%s' of unknown size", + ctitem->ct_name); + return NULL; + } + if (ctitem->ct_flags & CT_PRIMITIVE_CHAR) + datasize *= 2; /* forcefully add another character: a null */ + + if (ctitem->ct_flags & (CT_STRUCT | CT_UNION)) { + if (force_lazy_struct(ctitem) < 0) /* for CT_WITH_VAR_ARRAY */ + return NULL; + + if (ctitem->ct_flags & CT_WITH_VAR_ARRAY) { + assert(ct->ct_flags & CT_IS_PTR_TO_OWNED); + dataoffset = offsetof(CDataObject_own_length, alignment); + + if (init != Py_None) { + Py_ssize_t optvarsize = datasize; + if (convert_struct_from_object(NULL, ctitem, init, + &optvarsize) < 0) + return NULL; + datasize = optvarsize; + } + } + } + } + else if (ct->ct_flags & CT_ARRAY) { + dataoffset = offsetof(CDataObject_own_nolength, alignment); + datasize = ct->ct_size; + if (datasize < 0) { + explicitlength = get_new_array_length(ct->ct_itemdescr, &init); + if (explicitlength < 0) + return NULL; + ctitem = ct->ct_itemdescr; + dataoffset = offsetof(CDataObject_own_length, alignment); + datasize = MUL_WRAPAROUND(explicitlength, ctitem->ct_size); + if (explicitlength > 0 && + (datasize / explicitlength) != ctitem->ct_size) { + PyErr_SetString(PyExc_OverflowError, + "array size would overflow a Py_ssize_t"); + return NULL; + } + } + } + else { + PyErr_Format(PyExc_TypeError, + "expected a pointer or array ctype, got '%s'", + ct->ct_name); + return NULL; + } + + if (ct->ct_flags & CT_IS_PTR_TO_OWNED) { + /* common case of ptr-to-struct (or ptr-to-union): for this case + we build two objects instead of one, with the memory-owning + one being really the struct (or union) and the returned one + having a strong reference to it */ + CDataObject *cds; + + cds = allocate_with_allocator(dataoffset, datasize, ct->ct_itemdescr, + allocator); + if (cds == NULL) + return NULL; + + cd = allocate_owning_object(sizeof(CDataObject_own_structptr), ct, + /*dont_clear=*/1); + if (cd == NULL) { + Py_DECREF(cds); + return NULL; + } + /* store the only reference to cds into cd */ + ((CDataObject_own_structptr *)cd)->structobj = (PyObject *)cds; + /* store information about the allocated size of the struct */ + if (dataoffset == offsetof(CDataObject_own_length, alignment)) { + ((CDataObject_own_length *)cds)->length = datasize; + } + assert(explicitlength < 0); + + cd->c_data = cds->c_data; + } + else { + cd = allocate_with_allocator(dataoffset, datasize, ct, allocator); + if (cd == NULL) + return NULL; + + if (explicitlength >= 0) + ((CDataObject_own_length*)cd)->length = explicitlength; + } + + if (init != Py_None) { + if (convert_from_object(cd->c_data, + (ct->ct_flags & CT_POINTER) ? ct->ct_itemdescr : ct, init) < 0) { + Py_DECREF(cd); + return NULL; + } + } + return (PyObject *)cd; +} + +static PyObject *b_newp(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *init = Py_None; + if (!PyArg_ParseTuple(args, "O!|O:newp", &CTypeDescr_Type, &ct, &init)) + return NULL; + return direct_newp(ct, init, &default_allocator); +} + +static int +_my_PyObject_AsBool(PyObject *ob) +{ + /* convert and cast a Python object to a boolean. Accept an integer + or a float object, up to a CData 'long double'. */ + PyObject *io; + PyNumberMethods *nb; + int res; + +#if PY_MAJOR_VERSION < 3 + if (PyInt_Check(ob)) { + return PyInt_AS_LONG(ob) != 0; + } + else +#endif + if (PyLong_Check(ob)) { + return _PyLong_Sign(ob) != 0; + } + else if (PyFloat_Check(ob)) { + return PyFloat_AS_DOUBLE(ob) != 0.0; + } + else if (CData_Check(ob)) { + CDataObject *cd = (CDataObject *)ob; + if (cd->c_type->ct_flags & CT_PRIMITIVE_FLOAT) { + /*READ(cd->c_data, cd->c_type->ct_size)*/ + if (cd->c_type->ct_flags & CT_IS_LONGDOUBLE) { + /* 'long double' objects: return the answer directly */ + return read_raw_longdouble_data(cd->c_data) != 0.0; + } + else { + /* 'float'/'double' objects: return the answer directly */ + return read_raw_float_data(cd->c_data, + cd->c_type->ct_size) != 0.0; + } + } + } + nb = ob->ob_type->tp_as_number; + if (nb == NULL || (nb->nb_float == NULL && nb->nb_int == NULL)) { + PyErr_SetString(PyExc_TypeError, "integer/float expected"); + return -1; + } + if (nb->nb_float && !CData_Check(ob)) + io = (*nb->nb_float) (ob); + else + io = (*nb->nb_int) (ob); + if (io == NULL) + return -1; + + if (PyIntOrLong_Check(io) || PyFloat_Check(io)) { + res = _my_PyObject_AsBool(io); + } + else { + PyErr_SetString(PyExc_TypeError, "integer/float conversion failed"); + res = -1; + } + Py_DECREF(io); + return res; +} + +static CDataObject *_new_casted_primitive(CTypeDescrObject *ct) +{ + int dataoffset = offsetof(CDataObject_casted_primitive, alignment); + CDataObject *cd = (CDataObject *)PyObject_Malloc(dataoffset + ct->ct_size); + if (PyObject_Init((PyObject *)cd, &CData_Type) == NULL) + return NULL; + Py_INCREF(ct); + cd->c_type = ct; + cd->c_data = ((char*)cd) + dataoffset; + cd->c_weakreflist = NULL; + return cd; +} + +static CDataObject *cast_to_integer_or_char(CTypeDescrObject *ct, PyObject *ob) +{ + unsigned PY_LONG_LONG value; + CDataObject *cd; + + if (CData_Check(ob) && + ((CDataObject *)ob)->c_type->ct_flags & + (CT_POINTER|CT_FUNCTIONPTR|CT_ARRAY)) { + value = (Py_intptr_t)((CDataObject *)ob)->c_data; + } +#if PY_MAJOR_VERSION < 3 + else if (PyString_Check(ob)) { + if (PyString_GET_SIZE(ob) != 1) { + PyErr_Format(PyExc_TypeError, + "cannot cast string of length %zd to ctype '%s'", + PyString_GET_SIZE(ob), ct->ct_name); + return NULL; + } + value = (unsigned char)PyString_AS_STRING(ob)[0]; + } +#endif + else if (PyUnicode_Check(ob)) { + char err_buf[80]; + cffi_char32_t ordinal; + if (_my_PyUnicode_AsSingleChar32(ob, &ordinal, err_buf) < 0) { + PyErr_Format(PyExc_TypeError, + "cannot cast %s to ctype '%s'", err_buf, ct->ct_name); + return NULL; + } + /* the types char16_t and char32_t are both unsigned. However, + wchar_t might be signed. In theory it does not matter, + because 'ordinal' comes from a regular Python unicode. */ +#ifdef HAVE_WCHAR_H + if (ct->ct_flags & CT_IS_SIGNED_WCHAR) + value = (wchar_t)ordinal; + else +#endif + value = ordinal; + } + else if (PyBytes_Check(ob)) { + int res = _convert_to_char(ob); + if (res < 0) + return NULL; + value = (unsigned char)res; + } + else if (ct->ct_flags & CT_IS_BOOL) { + int res = _my_PyObject_AsBool(ob); + if (res < 0) + return NULL; + value = res; + } + else { + value = _my_PyLong_AsUnsignedLongLong(ob, 0); + if (value == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) + return NULL; + } + if (ct->ct_flags & CT_IS_BOOL) + value = !!value; + cd = _new_casted_primitive(ct); + if (cd != NULL) + write_raw_integer_data(cd->c_data, value, ct->ct_size); + return cd; +} + +/* returns -1 if cannot cast, 0 if we don't get a value, 1 if we do */ +static int check_bytes_for_float_compatible(PyObject *io, double *out_value) +{ + if (PyBytes_Check(io)) { + if (PyBytes_GET_SIZE(io) != 1) + goto error; + *out_value = (unsigned char)PyBytes_AS_STRING(io)[0]; + return 1; + } + else if (PyUnicode_Check(io)) { + char ignored[80]; + cffi_char32_t ordinal; + if (_my_PyUnicode_AsSingleChar32(io, &ordinal, ignored) < 0) + goto error; + /* the signness of the 32-bit version of wide chars should not + * matter here, because 'ordinal' comes from a normal Python + * unicode string */ + *out_value = ordinal; + return 1; + } + *out_value = 0; /* silence a gcc warning if this function is inlined */ + return 0; + + error: + Py_DECREF(io); + *out_value = 0; /* silence a gcc warning if this function is inlined */ + return -1; +} + +static PyObject *do_cast(CTypeDescrObject *ct, PyObject *ob) +{ + CDataObject *cd; + + if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR|CT_ARRAY) && + ct->ct_size >= 0) { + /* cast to a pointer, to a funcptr, or to an array. + Note that casting to an array is an extension to the C language, + which seems to be necessary in order to sanely get a + at some address. */ + unsigned PY_LONG_LONG value; + + if (CData_Check(ob)) { + CDataObject *cdsrc = (CDataObject *)ob; + if (cdsrc->c_type->ct_flags & + (CT_POINTER|CT_FUNCTIONPTR|CT_ARRAY)) { + return new_simple_cdata(cdsrc->c_data, ct); + } + } + if ((ct->ct_flags & CT_POINTER) && + (ct->ct_itemdescr->ct_flags & CT_IS_FILE) && + PyFile_Check(ob)) { + FILE *f = PyFile_AsFile(ob); + if (f == NULL && PyErr_Occurred()) + return NULL; + return new_simple_cdata((char *)f, ct); + } + value = _my_PyLong_AsUnsignedLongLong(ob, 0); + if (value == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) + return NULL; + return new_simple_cdata((char *)(Py_intptr_t)value, ct); + } + else if (ct->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_UNSIGNED + |CT_PRIMITIVE_CHAR)) { + /* cast to an integer type or a char */ + return (PyObject *)cast_to_integer_or_char(ct, ob); + } + else if (ct->ct_flags & CT_PRIMITIVE_FLOAT) { + /* cast to a float */ + double value; + PyObject *io; + int res; + + if (CData_Check(ob)) { + CDataObject *cdsrc = (CDataObject *)ob; + + if (!(cdsrc->c_type->ct_flags & CT_PRIMITIVE_ANY)) + goto cannot_cast; + io = convert_to_object(cdsrc->c_data, cdsrc->c_type); + if (io == NULL) + return NULL; + } + else { + io = ob; + Py_INCREF(io); + } + + res = check_bytes_for_float_compatible(io, &value); + if (res == -1) + goto cannot_cast; + if (res == 0) { + if ((ct->ct_flags & CT_IS_LONGDOUBLE) && + CData_Check(io) && + (((CDataObject *)io)->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + long double lvalue; + char *data = ((CDataObject *)io)->c_data; + /*READ(data, sizeof(long double)*/ + lvalue = read_raw_longdouble_data(data); + Py_DECREF(io); + cd = _new_casted_primitive(ct); + if (cd != NULL) + write_raw_longdouble_data(cd->c_data, lvalue); + return (PyObject *)cd; + } + value = PyFloat_AsDouble(io); + } + Py_DECREF(io); + if (value == -1.0 && PyErr_Occurred()) + return NULL; + + cd = _new_casted_primitive(ct); + if (cd != NULL) { + if (!(ct->ct_flags & CT_IS_LONGDOUBLE)) + write_raw_float_data(cd->c_data, value, ct->ct_size); + else + write_raw_longdouble_data(cd->c_data, (long double)value); + } + return (PyObject *)cd; + } + else if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) { + /* cast to a complex */ + Py_complex value; + PyObject *io; + int res; + + if (CData_Check(ob)) { + CDataObject *cdsrc = (CDataObject *)ob; + + if (!(cdsrc->c_type->ct_flags & CT_PRIMITIVE_ANY)) + goto cannot_cast; + io = convert_to_object(cdsrc->c_data, cdsrc->c_type); + if (io == NULL) + return NULL; + } + else { + io = ob; + Py_INCREF(io); + } + + res = check_bytes_for_float_compatible(io, &value.real); + if (res == -1) + goto cannot_cast; + if (res == 1) { + // got it from string + value.imag = 0.0; + } else { + value = PyComplex_AsCComplex(io); + } + Py_DECREF(io); + if (PyErr_Occurred()) { + return NULL; + } + cd = _new_casted_primitive(ct); + if (cd != NULL) { + write_raw_complex_data(cd->c_data, value, ct->ct_size); + } + return (PyObject *)cd; + } + else { + PyErr_Format(PyExc_TypeError, "cannot cast to ctype '%s'", + ct->ct_name); + return NULL; + } + + cannot_cast: + if (CData_Check(ob)) + PyErr_Format(PyExc_TypeError, "cannot cast ctype '%s' to ctype '%s'", + ((CDataObject *)ob)->c_type->ct_name, ct->ct_name); + else + PyErr_Format(PyExc_TypeError, + "cannot cast %.200s object to ctype '%s'", + Py_TYPE(ob)->tp_name, ct->ct_name); + return NULL; +} + +static PyObject *b_cast(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *ob; + if (!PyArg_ParseTuple(args, "O!O:cast", &CTypeDescr_Type, &ct, &ob)) + return NULL; + + return do_cast(ct, ob); +} + +/************************************************************/ + +typedef struct { + PyObject_HEAD + void *dl_handle; + char *dl_name; +} DynLibObject; + +static void dl_dealloc(DynLibObject *dlobj) +{ + if (dlobj->dl_handle != NULL) + dlclose(dlobj->dl_handle); + free(dlobj->dl_name); + PyObject_Del(dlobj); +} + +static PyObject *dl_repr(DynLibObject *dlobj) +{ + return PyText_FromFormat("", dlobj->dl_name); +} + +static int dl_check_closed(DynLibObject *dlobj) +{ + if (dlobj->dl_handle == NULL) + { + PyErr_Format(PyExc_ValueError, "library '%s' has already been closed", + dlobj->dl_name); + return -1; + } + return 0; +} + +static PyObject *dl_load_function(DynLibObject *dlobj, PyObject *args) +{ + CTypeDescrObject *ct; + char *funcname; + void *funcptr; + + if (!PyArg_ParseTuple(args, "O!s:load_function", + &CTypeDescr_Type, &ct, &funcname)) + return NULL; + + if (dl_check_closed(dlobj) < 0) + return NULL; + + if (!(ct->ct_flags & (CT_FUNCTIONPTR | CT_POINTER | CT_ARRAY))) { + PyErr_Format(PyExc_TypeError, + "function or pointer or array cdata expected, got '%s'", + ct->ct_name); + return NULL; + } + dlerror(); /* clear error condition */ + funcptr = dlsym(dlobj->dl_handle, funcname); + if (funcptr == NULL) { + const char *error = dlerror(); + PyErr_Format(PyExc_AttributeError, + "function/symbol '%s' not found in library '%s': %s", + funcname, dlobj->dl_name, error); + return NULL; + } + + if ((ct->ct_flags & CT_ARRAY) && ct->ct_length < 0) { + ct = (CTypeDescrObject *)ct->ct_stuff; + } + return new_simple_cdata(funcptr, ct); +} + +static PyObject *dl_read_variable(DynLibObject *dlobj, PyObject *args) +{ + CTypeDescrObject *ct; + char *varname; + char *data; + + if (!PyArg_ParseTuple(args, "O!s:read_variable", + &CTypeDescr_Type, &ct, &varname)) + return NULL; + + if (dl_check_closed(dlobj) < 0) + return NULL; + + dlerror(); /* clear error condition */ + data = dlsym(dlobj->dl_handle, varname); + if (data == NULL) { + const char *error = dlerror(); + if (error != NULL) { + PyErr_Format(PyExc_KeyError, + "variable '%s' not found in library '%s': %s", + varname, dlobj->dl_name, error); + return NULL; + } + } + return convert_to_object(data, ct); +} + +static PyObject *dl_write_variable(DynLibObject *dlobj, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *value; + char *varname; + char *data; + + if (!PyArg_ParseTuple(args, "O!sO:write_variable", + &CTypeDescr_Type, &ct, &varname, &value)) + return NULL; + + if (dl_check_closed(dlobj) < 0) + return NULL; + + dlerror(); /* clear error condition */ + data = dlsym(dlobj->dl_handle, varname); + if (data == NULL) { + const char *error = dlerror(); + PyErr_Format(PyExc_KeyError, + "variable '%s' not found in library '%s': %s", + varname, dlobj->dl_name, error); + return NULL; + } + if (convert_from_object(data, ct, value) < 0) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *dl_close_lib(DynLibObject *dlobj, PyObject *no_args) +{ + if (dlobj->dl_handle != NULL) + { + dlclose(dlobj->dl_handle); + dlobj->dl_handle = NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef dl_methods[] = { + {"load_function", (PyCFunction)dl_load_function, METH_VARARGS}, + {"read_variable", (PyCFunction)dl_read_variable, METH_VARARGS}, + {"write_variable", (PyCFunction)dl_write_variable, METH_VARARGS}, + {"close_lib", (PyCFunction)dl_close_lib, METH_NOARGS}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject dl_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.Library", /* tp_name */ + sizeof(DynLibObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dl_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)dl_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + dl_methods, /* tp_methods */ +}; + +static void *b_do_dlopen(PyObject *args, const char **p_printable_filename, + PyObject **p_temp) +{ + /* Logic to call the correct version of dlopen(). Returns NULL in case of error. + Otherwise, '*p_printable_filename' will point to a printable char version of + the filename (maybe utf-8-encoded). '*p_temp' will be set either to NULL or + to a temporary object that must be freed after looking at printable_filename. + */ + void *handle; + char *filename_or_null; + int flags = 0; + *p_temp = NULL; + + if (PyTuple_GET_SIZE(args) == 0 || PyTuple_GET_ITEM(args, 0) == Py_None) { + PyObject *dummy; + if (!PyArg_ParseTuple(args, "|Oi:load_library", + &dummy, &flags)) + return NULL; + filename_or_null = NULL; + *p_printable_filename = ""; + } + else + { + PyObject *s = PyTuple_GET_ITEM(args, 0); +#ifdef MS_WIN32 + Py_UNICODE *filenameW; + if (PyArg_ParseTuple(args, "u|i:load_library", &filenameW, &flags)) + { +#if PY_MAJOR_VERSION < 3 + s = PyUnicode_AsUTF8String(s); + if (s == NULL) + return NULL; + *p_temp = s; +#endif + *p_printable_filename = PyText_AsUTF8(s); + if (*p_printable_filename == NULL) + return NULL; + + handle = dlopenW(filenameW); + goto got_handle; + } + PyErr_Clear(); +#endif + if (!PyArg_ParseTuple(args, "et|i:load_library", + Py_FileSystemDefaultEncoding, &filename_or_null, &flags)) + return NULL; +#if PY_MAJOR_VERSION < 3 + if (PyUnicode_Check(s)) + { + s = PyUnicode_AsUTF8String(s); + if (s == NULL) + return NULL; + *p_temp = s; + } +#endif + *p_printable_filename = PyText_AsUTF8(s); + if (*p_printable_filename == NULL) + return NULL; + } + if ((flags & (RTLD_NOW | RTLD_LAZY)) == 0) + flags |= RTLD_NOW; + + handle = dlopen(filename_or_null, flags); + +#ifdef MS_WIN32 + got_handle: +#endif + if (handle == NULL) { + const char *error = dlerror(); + PyErr_Format(PyExc_OSError, "cannot load library '%s': %s", + *p_printable_filename, error); + return NULL; + } + return handle; +} + +static PyObject *b_load_library(PyObject *self, PyObject *args) +{ + const char *printable_filename; + PyObject *temp; + void *handle; + DynLibObject *dlobj = NULL; + + handle = b_do_dlopen(args, &printable_filename, &temp); + if (handle == NULL) + goto error; + + dlobj = PyObject_New(DynLibObject, &dl_type); + if (dlobj == NULL) { + dlclose(handle); + goto error; + } + dlobj->dl_handle = handle; + dlobj->dl_name = strdup(printable_filename); + + error: + Py_XDECREF(temp); + return (PyObject *)dlobj; +} + +/************************************************************/ + +static PyObject *get_unique_type(CTypeDescrObject *x, + const void *unique_key[], long keylength) +{ + /* Replace the CTypeDescrObject 'x' with a standardized one. + This either just returns x, or x is decrefed and a new reference + to the already-existing equivalent is returned. + + In this function, 'x' always contains a reference that must be + either decrefed or returned. + + Keys: + void ["void"] + primitive [&static_struct] + pointer [ctype] + array [ctype, length] + funcptr [ctresult, ellipsis+abi, num_args, ctargs...] + */ + PyObject *key, *y; + void *pkey; + + key = PyBytes_FromStringAndSize(NULL, keylength * sizeof(void *)); + if (key == NULL) + goto error; + + pkey = PyBytes_AS_STRING(key); + memcpy(pkey, unique_key, keylength * sizeof(void *)); + + y = PyDict_GetItem(unique_cache, key); + if (y != NULL) { + Py_DECREF(key); + Py_INCREF(y); + Py_DECREF(x); + return y; + } + if (PyDict_SetItem(unique_cache, key, (PyObject *)x) < 0) { + Py_DECREF(key); + goto error; + } + /* Haaaack for our reference count hack: gcmodule.c must not see this + dictionary. The problem is that any PyDict_SetItem() notices that + 'x' is tracked and re-tracks the unique_cache dictionary. So here + we re-untrack it again... */ + PyObject_GC_UnTrack(unique_cache); + + assert(x->ct_unique_key == NULL); + x->ct_unique_key = key; /* the key will be freed in ctypedescr_dealloc() */ + /* the 'value' in unique_cache doesn't count as 1, but don't use + Py_DECREF(x) here because it will confuse debug builds into thinking + there was an extra DECREF in total. */ + ((PyObject *)x)->ob_refcnt--; + return (PyObject *)x; + + error: + Py_DECREF(x); + return NULL; +} + +/* according to the C standard, these types should be equivalent to the + _Complex types for the purposes of storage (not arguments in calls!) */ +typedef float cffi_float_complex_t[2]; +typedef double cffi_double_complex_t[2]; + +static PyObject *new_primitive_type(const char *name) +{ +#define ENUM_PRIMITIVE_TYPES \ + EPTYPE(c, char, CT_PRIMITIVE_CHAR) \ + EPTYPE(s, short, CT_PRIMITIVE_SIGNED ) \ + EPTYPE(i, int, CT_PRIMITIVE_SIGNED ) \ + EPTYPE(l, long, CT_PRIMITIVE_SIGNED ) \ + EPTYPE(ll, long long, CT_PRIMITIVE_SIGNED ) \ + EPTYPE(sc, signed char, CT_PRIMITIVE_SIGNED ) \ + EPTYPE(uc, unsigned char, CT_PRIMITIVE_UNSIGNED ) \ + EPTYPE(us, unsigned short, CT_PRIMITIVE_UNSIGNED ) \ + EPTYPE(ui, unsigned int, CT_PRIMITIVE_UNSIGNED ) \ + EPTYPE(ul, unsigned long, CT_PRIMITIVE_UNSIGNED ) \ + EPTYPE(ull, unsigned long long, CT_PRIMITIVE_UNSIGNED ) \ + EPTYPE(f, float, CT_PRIMITIVE_FLOAT ) \ + EPTYPE(d, double, CT_PRIMITIVE_FLOAT ) \ + EPTYPE(ld, long double, CT_PRIMITIVE_FLOAT | CT_IS_LONGDOUBLE ) \ + EPTYPE2(fc, "float _Complex", cffi_float_complex_t, CT_PRIMITIVE_COMPLEX ) \ + EPTYPE2(dc, "double _Complex", cffi_double_complex_t, CT_PRIMITIVE_COMPLEX ) \ + ENUM_PRIMITIVE_TYPES_WCHAR \ + EPTYPE2(c16, "char16_t", cffi_char16_t, CT_PRIMITIVE_CHAR ) \ + EPTYPE2(c32, "char32_t", cffi_char32_t, CT_PRIMITIVE_CHAR ) \ + EPTYPE(b, _Bool, CT_PRIMITIVE_UNSIGNED | CT_IS_BOOL ) \ + /* the following types are not primitive in the C sense */ \ + EPTYPE(i8, int8_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(u8, uint8_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(i16, int16_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(u16, uint16_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(i32, int32_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(u32, uint32_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(i64, int64_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(u64, uint64_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(il8, int_least8_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(ul8, uint_least8_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(il16, int_least16_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(ul16, uint_least16_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(il32, int_least32_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(ul32, uint_least32_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(il64, int_least64_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(ul64, uint_least64_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(if8, int_fast8_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(uf8, uint_fast8_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(if16, int_fast16_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(uf16, uint_fast16_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(if32, int_fast32_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(uf32, uint_fast32_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(if64, int_fast64_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(uf64, uint_fast64_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(ip, intptr_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(up, uintptr_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(im, intmax_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(um, uintmax_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE(pd, ptrdiff_t, CT_PRIMITIVE_SIGNED) \ + EPTYPE(sz, size_t, CT_PRIMITIVE_UNSIGNED) \ + EPTYPE2(ssz, "ssize_t", Py_ssize_t, CT_PRIMITIVE_SIGNED) + +#ifdef HAVE_WCHAR_H +# define ENUM_PRIMITIVE_TYPES_WCHAR \ + EPTYPE(wc, wchar_t, CT_PRIMITIVE_CHAR | \ + (((wchar_t)-1) > 0 ? 0 : CT_IS_SIGNED_WCHAR)) +#else +# define ENUM_PRIMITIVE_TYPES_WCHAR /* nothing */ +#endif + +#define EPTYPE(code, typename, flags) EPTYPE2(code, #typename, typename, flags) + +#define EPTYPE2(code, export_name, typename, flags) \ + struct aligncheck_##code { char x; typename y; }; + ENUM_PRIMITIVE_TYPES +#undef EPTYPE2 + + CTypeDescrObject *td; + static const struct descr_s { const char *name; int size, align, flags; } + types[] = { +#define EPTYPE2(code, export_name, typename, flags) \ + { export_name, \ + sizeof(typename), \ + offsetof(struct aligncheck_##code, y), \ + flags \ + }, + ENUM_PRIMITIVE_TYPES +#undef EPTYPE2 +#undef EPTYPE +#undef ENUM_PRIMITIVE_TYPES_WCHAR +#undef ENUM_PRIMITIVE_TYPES + { NULL } + }; + const struct descr_s *ptypes; + const void *unique_key[1]; + int name_size; + ffi_type *ffitype; + + for (ptypes=types; ; ptypes++) { + if (ptypes->name == NULL) { +#ifndef HAVE_WCHAR_H + if (strcmp(name, "wchar_t")) + PyErr_SetString(PyExc_NotImplementedError, name); + else +#endif + PyErr_SetString(PyExc_KeyError, name); + return NULL; + } + if (strcmp(name, ptypes->name) == 0) + break; + } + + if (ptypes->flags & CT_PRIMITIVE_SIGNED) { + switch (ptypes->size) { + case 1: ffitype = &ffi_type_sint8; break; + case 2: ffitype = &ffi_type_sint16; break; + case 4: ffitype = &ffi_type_sint32; break; + case 8: ffitype = &ffi_type_sint64; break; + default: goto bad_ffi_type; + } + } + else if (ptypes->flags & CT_PRIMITIVE_FLOAT) { + if (strcmp(ptypes->name, "float") == 0) + ffitype = &ffi_type_float; + else if (strcmp(ptypes->name, "double") == 0) + ffitype = &ffi_type_double; + else if (strcmp(ptypes->name, "long double") == 0) { + /* assume that if sizeof(double) == sizeof(long double), then + the two types are equivalent for C. libffi bugs on Win64 + if a function's return type is ffi_type_longdouble... */ + if (sizeof(double) == sizeof(long double)) + ffitype = &ffi_type_double; + else + ffitype = &ffi_type_longdouble; + } + else + goto bad_ffi_type; + } + else if (ptypes->flags & CT_PRIMITIVE_COMPLEX) { + /* As of March 2017, still no libffi support for complex. + It fails silently if we try to use ffi_type_complex_float + or ffi_type_complex_double. Better not use it at all. + */ + ffitype = NULL; + } + else { + switch (ptypes->size) { + case 1: ffitype = &ffi_type_uint8; break; + case 2: ffitype = &ffi_type_uint16; break; + case 4: ffitype = &ffi_type_uint32; break; + case 8: ffitype = &ffi_type_uint64; break; + default: goto bad_ffi_type; + } + } + + name_size = strlen(ptypes->name) + 1; + td = ctypedescr_new(name_size); + if (td == NULL) + return NULL; + + memcpy(td->ct_name, name, name_size); + td->ct_size = ptypes->size; + td->ct_length = ptypes->align; + td->ct_extra = ffitype; + td->ct_flags = ptypes->flags; + if (td->ct_flags & (CT_PRIMITIVE_SIGNED | CT_PRIMITIVE_CHAR)) { + if (td->ct_size <= (Py_ssize_t)sizeof(long)) + td->ct_flags |= CT_PRIMITIVE_FITS_LONG; + } + else if (td->ct_flags & CT_PRIMITIVE_UNSIGNED) { + if (td->ct_size < (Py_ssize_t)sizeof(long)) + td->ct_flags |= CT_PRIMITIVE_FITS_LONG; + } + td->ct_name_position = strlen(td->ct_name); + unique_key[0] = ptypes; + return get_unique_type(td, unique_key, 1); + + bad_ffi_type: + PyErr_Format(PyExc_NotImplementedError, + "primitive type '%s' has size %d; " + "the supported sizes are 1, 2, 4, 8", + name, (int)ptypes->size); + return NULL; +} + +static PyObject *b_new_primitive_type(PyObject *self, PyObject *args) +{ + char *name; + if (!PyArg_ParseTuple(args, "s:new_primitive_type", &name)) + return NULL; + return new_primitive_type(name); +} + +static PyObject *new_pointer_type(CTypeDescrObject *ctitem) +{ + CTypeDescrObject *td; + const char *extra; + const void *unique_key[1]; + + if (ctitem->ct_flags & CT_ARRAY) + extra = "(*)"; /* obscure case: see test_array_add */ + else + extra = " *"; + td = ctypedescr_new_on_top(ctitem, extra, 2); + if (td == NULL) + return NULL; + + td->ct_size = sizeof(void *); + td->ct_length = -1; + td->ct_flags = CT_POINTER; + if (ctitem->ct_flags & (CT_STRUCT|CT_UNION)) + td->ct_flags |= CT_IS_PTR_TO_OWNED; + if (ctitem->ct_flags & CT_VOID) + td->ct_flags |= CT_IS_VOID_PTR; + if ((ctitem->ct_flags & CT_VOID) || + ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) && + ctitem->ct_size == sizeof(char))) + td->ct_flags |= CT_IS_VOIDCHAR_PTR; /* 'void *' or 'char *' only */ + unique_key[0] = ctitem; + return get_unique_type(td, unique_key, 1); +} + +static PyObject *b_new_pointer_type(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ctitem; + if (!PyArg_ParseTuple(args, "O!:new_pointer_type", + &CTypeDescr_Type, &ctitem)) + return NULL; + return new_pointer_type(ctitem); +} + +static PyObject *b_new_array_type(PyObject *self, PyObject *args) +{ + PyObject *lengthobj; + Py_ssize_t length; + CTypeDescrObject *ctptr; + + if (!PyArg_ParseTuple(args, "O!O:new_array_type", + &CTypeDescr_Type, &ctptr, &lengthobj)) + return NULL; + + if (lengthobj == Py_None) { + length = -1; + } + else { + length = PyNumber_AsSsize_t(lengthobj, PyExc_OverflowError); + if (length < 0) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, "negative array length"); + return NULL; + } + } + return new_array_type(ctptr, length); +} + +static PyObject * +new_array_type(CTypeDescrObject *ctptr, Py_ssize_t length) +{ + CTypeDescrObject *td, *ctitem; + char extra_text[32]; + Py_ssize_t arraysize; + int flags = CT_ARRAY; + const void *unique_key[2]; + + if (!(ctptr->ct_flags & CT_POINTER)) { + PyErr_SetString(PyExc_TypeError, "first arg must be a pointer ctype"); + return NULL; + } + ctitem = ctptr->ct_itemdescr; + if (ctitem->ct_size < 0) { + PyErr_Format(PyExc_ValueError, "array item of unknown size: '%s'", + ctitem->ct_name); + return NULL; + } + + if (length < 0) { + sprintf(extra_text, "[]"); + length = -1; + arraysize = -1; + } + else { + sprintf(extra_text, "[%llu]", (unsigned PY_LONG_LONG)length); + arraysize = MUL_WRAPAROUND(length, ctitem->ct_size); + if (length > 0 && (arraysize / length) != ctitem->ct_size) { + PyErr_SetString(PyExc_OverflowError, + "array size would overflow a Py_ssize_t"); + return NULL; + } + } + td = ctypedescr_new_on_top(ctitem, extra_text, 0); + if (td == NULL) + return NULL; + + Py_INCREF(ctptr); + td->ct_stuff = (PyObject *)ctptr; + td->ct_size = arraysize; + td->ct_length = length; + td->ct_flags = flags; + unique_key[0] = ctptr; + unique_key[1] = (void *)length; + return get_unique_type(td, unique_key, 2); +} + +static PyObject *new_void_type(void) +{ + int name_size = strlen("void") + 1; + const void *unique_key[1]; + CTypeDescrObject *td = ctypedescr_new(name_size); + if (td == NULL) + return NULL; + + memcpy(td->ct_name, "void", name_size); + td->ct_size = -1; + td->ct_flags = CT_VOID | CT_IS_OPAQUE; + td->ct_name_position = strlen("void"); + unique_key[0] = "void"; + return get_unique_type(td, unique_key, 1); +} + +static PyObject *b_new_void_type(PyObject *self, PyObject *args) +{ + return new_void_type(); +} + +static PyObject *new_struct_or_union_type(const char *name, int flag) +{ + int namelen = strlen(name); + CTypeDescrObject *td = ctypedescr_new(namelen + 1); + if (td == NULL) + return NULL; + + td->ct_size = -1; + td->ct_length = -1; + td->ct_flags = flag | CT_IS_OPAQUE; + td->ct_extra = NULL; + memcpy(td->ct_name, name, namelen + 1); + td->ct_name_position = namelen; + return (PyObject *)td; +} + +static PyObject *b_new_struct_type(PyObject *self, PyObject *args) +{ + char *name; + int flag; + if (!PyArg_ParseTuple(args, "s:new_struct_type", &name)) + return NULL; + + flag = CT_STRUCT; + if (strcmp(name, "struct _IO_FILE") == 0 || strcmp(name, "FILE") == 0) + flag |= CT_IS_FILE; + return new_struct_or_union_type(name, flag); +} + +static PyObject *b_new_union_type(PyObject *self, PyObject *args) +{ + char *name; + if (!PyArg_ParseTuple(args, "s:new_union_type", &name)) + return NULL; + return new_struct_or_union_type(name, CT_UNION); +} + +static CFieldObject * +_add_field(PyObject *interned_fields, PyObject *fname, CTypeDescrObject *ftype, + Py_ssize_t offset, int bitshift, int fbitsize, int flags) +{ + int err; + Py_ssize_t prev_size; + CFieldObject *cf = PyObject_New(CFieldObject, &CField_Type); + if (cf == NULL) + return NULL; + + Py_INCREF(ftype); + cf->cf_type = ftype; + cf->cf_offset = offset; + cf->cf_bitshift = bitshift; + cf->cf_bitsize = fbitsize; + cf->cf_flags = flags; + + Py_INCREF(fname); + PyText_InternInPlace(&fname); + prev_size = PyDict_Size(interned_fields); + err = PyDict_SetItem(interned_fields, fname, (PyObject *)cf); + Py_DECREF(fname); + Py_DECREF(cf); + if (err < 0) + return NULL; + + if (PyDict_Size(interned_fields) != prev_size + 1) { + PyErr_Format(PyExc_KeyError, "duplicate field name '%s'", + PyText_AS_UTF8(fname)); + return NULL; + } + return cf; /* borrowed reference */ +} + +#define SF_MSVC_BITFIELDS 0x01 +#define SF_GCC_ARM_BITFIELDS 0x02 +#define SF_GCC_X86_BITFIELDS 0x10 + +#define SF_GCC_BIG_ENDIAN 0x04 +#define SF_GCC_LITTLE_ENDIAN 0x40 + +#define SF_PACKED 0x08 +#define SF_STD_FIELD_POS 0x80 + +#ifdef MS_WIN32 +# define SF_DEFAULT_PACKING 8 +#else +# define SF_DEFAULT_PACKING 0x40000000 /* a huge power of two */ +#endif + +static int complete_sflags(int sflags) +{ + /* add one of the SF_xxx_BITFIELDS flags if none is specified */ + if (!(sflags & (SF_MSVC_BITFIELDS | SF_GCC_ARM_BITFIELDS | + SF_GCC_X86_BITFIELDS))) { +#ifdef MS_WIN32 + sflags |= SF_MSVC_BITFIELDS; +#else +# if defined(__arm__) || defined(__aarch64__) + sflags |= SF_GCC_ARM_BITFIELDS; +# else + sflags |= SF_GCC_X86_BITFIELDS; +# endif +#endif + } + /* add one of SF_GCC_xx_ENDIAN if none is specified */ + if (!(sflags & (SF_GCC_BIG_ENDIAN | SF_GCC_LITTLE_ENDIAN))) { + int _check_endian = 1; + if (*(char *)&_check_endian == 0) + sflags |= SF_GCC_BIG_ENDIAN; + else + sflags |= SF_GCC_LITTLE_ENDIAN; + } + return sflags; +} + +static int detect_custom_layout(CTypeDescrObject *ct, int sflags, + Py_ssize_t cdef_value, + Py_ssize_t compiler_value, + const char *msg1, const char *txt, + const char *msg2) +{ + if (compiler_value != cdef_value) { + if (sflags & SF_STD_FIELD_POS) { + PyErr_Format(FFIError, + "%s: %s%s%s (cdef says %zd, but C compiler says %zd)." + " fix it or use \"...;\" in the cdef for %s to " + "make it flexible", + ct->ct_name, msg1, txt, msg2, + cdef_value, compiler_value, + ct->ct_name); + return -1; + } + ct->ct_flags |= CT_CUSTOM_FIELD_POS; + } + return 0; +} + +static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *fields, *interned_fields, *ignored; + int is_union, alignment; + Py_ssize_t boffset, i, nb_fields, boffsetmax, alignedsize, boffsetorg; + Py_ssize_t totalsize = -1; + int totalalignment = -1; + CFieldObject **previous; + int prev_bitfield_size, prev_bitfield_free; + int sflags = 0, fflags; + int pack = 0; + + if (!PyArg_ParseTuple(args, "O!O!|Oniii:complete_struct_or_union", + &CTypeDescr_Type, &ct, + &PyList_Type, &fields, + &ignored, &totalsize, &totalalignment, &sflags, + &pack)) + return NULL; + + sflags = complete_sflags(sflags); + if (sflags & SF_PACKED) + pack = 1; + else if (pack <= 0) + pack = SF_DEFAULT_PACKING; + else + sflags |= SF_PACKED; + + if ((ct->ct_flags & (CT_STRUCT|CT_IS_OPAQUE)) == + (CT_STRUCT|CT_IS_OPAQUE)) { + is_union = 0; + } + else if ((ct->ct_flags & (CT_UNION|CT_IS_OPAQUE)) == + (CT_UNION|CT_IS_OPAQUE)) { + is_union = 1; + } + else { + PyErr_SetString(PyExc_TypeError, + "first arg must be a non-initialized struct or union ctype"); + return NULL; + } + ct->ct_flags &= ~(CT_CUSTOM_FIELD_POS | CT_WITH_PACKED_CHANGE); + + alignment = 1; + boffset = 0; /* this number is in *bits*, not bytes! */ + boffsetmax = 0; /* the maximum value of boffset, in bits too */ + prev_bitfield_size = 0; + prev_bitfield_free = 0; + nb_fields = PyList_GET_SIZE(fields); + interned_fields = PyDict_New(); + if (interned_fields == NULL) + return NULL; + + previous = (CFieldObject **)&ct->ct_extra; + + for (i=0; ict_size < 0) { + if ((ftype->ct_flags & CT_ARRAY) && fbitsize < 0 + && (i == nb_fields - 1 || foffset != -1)) { + ct->ct_flags |= CT_WITH_VAR_ARRAY; + } + else { + PyErr_Format(PyExc_TypeError, + "field '%s.%s' has ctype '%s' of unknown size", + ct->ct_name, PyText_AS_UTF8(fname), + ftype->ct_name); + goto error; + } + } + + if (is_union) + boffset = 0; /* reset each field at offset 0 */ + + /* update the total alignment requirement, but skip it if the + field is an anonymous bitfield or if SF_PACKED */ + falignorg = get_alignment(ftype); + if (falignorg < 0) + goto error; + falign = (pack < falignorg) ? pack : falignorg; + + do_align = 1; + if (!(sflags & SF_GCC_ARM_BITFIELDS) && fbitsize >= 0) { + if (!(sflags & SF_MSVC_BITFIELDS)) { + /* GCC: anonymous bitfields (of any size) don't cause alignment */ + do_align = PyText_GetSize(fname) > 0; + } + else { + /* MSVC: zero-sized bitfields don't cause alignment */ + do_align = fbitsize > 0; + } + } + if (alignment < falign && do_align) + alignment = falign; + + fflags = (is_union && i > 0) ? BF_IGNORE_IN_CTOR : 0; + + if (fbitsize < 0) { + /* not a bitfield: common case */ + int bs_flag; + + if ((ftype->ct_flags & CT_ARRAY) && ftype->ct_length <= 0) + bs_flag = BS_EMPTY_ARRAY; + else + 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, + except to know if we must set CT_CUSTOM_FIELD_POS */ + if (detect_custom_layout(ct, sflags, boffset / 8, foffset, + "wrong offset for field '", + PyText_AS_UTF8(fname), "'") < 0) + goto error; + boffset = foffset * 8; + } + + if (PyText_GetSize(fname) == 0 && + ftype->ct_flags & (CT_STRUCT|CT_UNION)) { + /* a nested anonymous struct or union */ + CFieldObject *cfsrc = (CFieldObject *)ftype->ct_extra; + for (; cfsrc != NULL; cfsrc = cfsrc->cf_next) { + /* broken complexity in the call to get_field_name(), + but we'll assume you never do that with nested + anonymous structures with thousand of fields */ + *previous = _add_field(interned_fields, + get_field_name(ftype, cfsrc), + cfsrc->cf_type, + boffset / 8 + cfsrc->cf_offset, + cfsrc->cf_bitshift, + cfsrc->cf_bitsize, + cfsrc->cf_flags | fflags); + if (*previous == NULL) + goto error; + previous = &(*previous)->cf_next; + } + /* always forbid such structures from being passed by value */ + ct->ct_flags |= CT_CUSTOM_FIELD_POS; + } + else { + *previous = _add_field(interned_fields, fname, ftype, + boffset / 8, bs_flag, -1, fflags); + if (*previous == NULL) + goto error; + previous = &(*previous)->cf_next; + } + if (ftype->ct_size >= 0) + boffset += ftype->ct_size * 8; + prev_bitfield_size = 0; + } + else { + /* this is the case of a bitfield */ + Py_ssize_t field_offset_bytes; + int bits_already_occupied, bitshift; + + if (foffset >= 0) { + PyErr_Format(PyExc_TypeError, + "field '%s.%s' is a bitfield, " + "but a fixed offset is specified", + ct->ct_name, PyText_AS_UTF8(fname)); + goto error; + } + + if (!(ftype->ct_flags & (CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED | + CT_PRIMITIVE_CHAR))) { + PyErr_Format(PyExc_TypeError, + "field '%s.%s' declared as '%s' cannot be a bit field", + ct->ct_name, PyText_AS_UTF8(fname), + ftype->ct_name); + goto error; + } + if (fbitsize > 8 * ftype->ct_size) { + PyErr_Format(PyExc_TypeError, + "bit field '%s.%s' is declared '%s:%d', which " + "exceeds the width of the type", + ct->ct_name, PyText_AS_UTF8(fname), + ftype->ct_name, fbitsize); + goto error; + } + + /* compute the starting position of the theoretical field + that covers a complete 'ftype', inside of which we will + locate the real bitfield */ + field_offset_bytes = boffset / 8; + field_offset_bytes &= ~(falign - 1); + + if (fbitsize == 0) { + if (PyText_GetSize(fname) > 0) { + PyErr_Format(PyExc_TypeError, + "field '%s.%s' is declared with :0", + ct->ct_name, PyText_AS_UTF8(fname)); + goto error; + } + if (!(sflags & SF_MSVC_BITFIELDS)) { + /* GCC's notion of "ftype :0;" */ + + /* pad boffset to a value aligned for "ftype" */ + if (boffset > field_offset_bytes * 8) { + field_offset_bytes += falign; + assert(boffset < field_offset_bytes * 8); + } + boffset = field_offset_bytes * 8; + } + else { + /* MSVC's notion of "ftype :0;" */ + + /* Mostly ignored. It seems they only serve as + separator between other bitfields, to force them + into separate words. */ + } + prev_bitfield_size = 0; + } + else { + if (!(sflags & SF_MSVC_BITFIELDS)) { + /* GCC's algorithm */ + + /* Can the field start at the offset given by 'boffset'? It + can if it would entirely fit into an aligned ftype field. */ + bits_already_occupied = boffset - (field_offset_bytes * 8); + + if (bits_already_occupied + fbitsize > 8 * ftype->ct_size) { + /* it would not fit, we need to start at the next + allowed position */ + if ((sflags & SF_PACKED) && + (bits_already_occupied & 7)) { + PyErr_Format(PyExc_NotImplementedError, + "with 'packed', gcc would compile field " + "'%s.%s' to reuse some bits in the previous " + "field", ct->ct_name, PyText_AS_UTF8(fname)); + goto error; + } + field_offset_bytes += falign; + assert(boffset < field_offset_bytes * 8); + boffset = field_offset_bytes * 8; + bitshift = 0; + } + else { + bitshift = bits_already_occupied; + assert(bitshift >= 0); + } + boffset += fbitsize; + } + else { + /* MSVC's algorithm */ + + /* A bitfield is considered as taking the full width + of their declared type. It can share some bits + with the previous field only if it was also a + bitfield and used a type of the same size. */ + if (prev_bitfield_size == ftype->ct_size && + prev_bitfield_free >= fbitsize) { + /* yes: reuse */ + bitshift = 8 * prev_bitfield_size - prev_bitfield_free; + } + else { + /* no: start a new full field */ + boffset = (boffset + falign*8-1) & ~(falign*8-1); /*align*/ + boffset += ftype->ct_size * 8; + bitshift = 0; + prev_bitfield_size = ftype->ct_size; + prev_bitfield_free = 8 * prev_bitfield_size; + } + prev_bitfield_free -= fbitsize; + field_offset_bytes = boffset / 8 - ftype->ct_size; + } + + if (sflags & SF_GCC_BIG_ENDIAN) + bitshift = 8 * ftype->ct_size - fbitsize - bitshift; + + *previous = _add_field(interned_fields, fname, ftype, + field_offset_bytes, bitshift, fbitsize, + fflags); + if (*previous == NULL) + goto error; + previous = &(*previous)->cf_next; + } + } + + if (boffset > boffsetmax) + boffsetmax = boffset; + } + *previous = NULL; + + /* Like C, if the size of this structure would be zero, we compute it + as 1 instead. But for ctypes support, we allow the manually- + specified totalsize to be zero in this case. */ + boffsetmax = (boffsetmax + 7) / 8; /* bits -> bytes */ + alignedsize = (boffsetmax + alignment - 1) & ~(alignment-1); + if (alignedsize == 0) + alignedsize = 1; + + if (totalsize < 0) { + totalsize = alignedsize; + } + else { + if (detect_custom_layout(ct, sflags, alignedsize, + totalsize, "wrong total size", "", "") < 0) + goto error; + if (totalsize < boffsetmax) { + PyErr_Format(PyExc_TypeError, + "%s cannot be of size %zd: there are fields at least " + "up to %zd", ct->ct_name, totalsize, boffsetmax); + goto error; + } + } + if (totalalignment < 0) { + totalalignment = alignment; + } + else { + if (detect_custom_layout(ct, sflags, alignment, totalalignment, + "wrong total alignment", "", "") < 0) + goto error; + } + + ct->ct_size = totalsize; + ct->ct_length = totalalignment; + ct->ct_stuff = interned_fields; + ct->ct_flags &= ~CT_IS_OPAQUE; + + Py_INCREF(Py_None); + return Py_None; + + error: + ct->ct_extra = NULL; + Py_DECREF(interned_fields); + return NULL; +} + +struct funcbuilder_s { + Py_ssize_t nb_bytes; + char *bufferp; + ffi_type **atypes; + ffi_type *rtype; + Py_ssize_t nargs; + CTypeDescrObject *fct; +}; + +static void *fb_alloc(struct funcbuilder_s *fb, Py_ssize_t size) +{ + if (fb->bufferp == NULL) { + fb->nb_bytes += size; + return NULL; + } + else { + char *result = fb->bufferp; + fb->bufferp += size; + return result; + } +} + +#define SUPPORTED_IN_API_MODE \ + " are only supported as %s if the function is " \ + "'API mode' and non-variadic (i.e. declared inside ffibuilder" \ + ".cdef()+ffibuilder.set_source() and not taking a final '...' " \ + "argument)" + +static ffi_type *fb_unsupported(CTypeDescrObject *ct, const char *place, + const char *detail) +{ + PyErr_Format(PyExc_NotImplementedError, + "ctype '%s' not supported as %s. %s. " + "Such structs" SUPPORTED_IN_API_MODE, + ct->ct_name, place, detail, place); + return NULL; +} + +static ffi_type *fb_fill_type(struct funcbuilder_s *fb, CTypeDescrObject *ct, + int is_result_type) +{ + const char *place = is_result_type ? "return value" : "argument"; + + if (ct->ct_flags & (CT_PRIMITIVE_ANY & ~CT_PRIMITIVE_COMPLEX)) { + return (ffi_type *)ct->ct_extra; + } + else if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) { + return &ffi_type_pointer; + } + else if ((ct->ct_flags & CT_VOID) && is_result_type) { + return &ffi_type_void; + } + + if (ct->ct_size <= 0) { + PyErr_Format(PyExc_TypeError, + ct->ct_size < 0 ? "ctype '%s' has incomplete type" + : "ctype '%s' has size 0", + ct->ct_name); + return NULL; + } + if (ct->ct_flags & CT_STRUCT) { + ffi_type *ffistruct, *ffifield; + ffi_type **elements; + Py_ssize_t i, n, nflat; + CFieldObject *cf; + + /* We can't pass a struct that was completed by verify(). + Issue: assume verify() is given "struct { long b; ...; }". + Then it will complete it in the same way whether it is actually + "struct { long a, b; }" or "struct { double a; long b; }". + But on 64-bit UNIX, these two structs are passed by value + differently: e.g. on x86-64, "b" ends up in register "rsi" in + the first case and "rdi" in the second case. + + 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). Note: it seems we only get in this case with + ffi.verify(). + */ + if (force_lazy_struct(ct) < 0) + return NULL; + if (ct->ct_flags & CT_CUSTOM_FIELD_POS) { + /* 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 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); + nflat = 0; + + /* walk the fields, expanding arrays into repetitions; first, + only count how many flattened fields there are */ + cf = (CFieldObject *)ct->ct_extra; + for (i=0; icf_bitshift >= 0) { + return fb_unsupported(ct, place, + "It is a struct with bit fields, which libffi does not " + "support"); + } + flat = 1; + ct1 = cf->cf_type; + while (ct1->ct_flags & CT_ARRAY) { + flat *= ct1->ct_length; + ct1 = ct1->ct_itemdescr; + } + if (flat <= 0) { + return fb_unsupported(ct, place, + "It is a struct with a zero-length array, which libffi " + "does not support"); + } + nflat += flat; + cf = cf->cf_next; + } + assert(cf == NULL); + + /* next, allocate and fill the flattened list */ + elements = fb_alloc(fb, (nflat + 1) * sizeof(ffi_type*)); + nflat = 0; + cf = (CFieldObject *)ct->ct_extra; + for (i=0; icf_type; + while (ct->ct_flags & CT_ARRAY) { + flat *= ct->ct_length; + ct = ct->ct_itemdescr; + } + ffifield = fb_fill_type(fb, ct, 0); + if (PyErr_Occurred()) + return NULL; + if (elements != NULL) { + for (j=0; jcf_next; + } + + /* finally, allocate the FFI_TYPE_STRUCT */ + ffistruct = fb_alloc(fb, sizeof(ffi_type)); + if (ffistruct != NULL) { + elements[nflat] = NULL; + ffistruct->size = ct->ct_size; + ffistruct->alignment = ct->ct_length; + ffistruct->type = FFI_TYPE_STRUCT; + ffistruct->elements = elements; + } + return ffistruct; + } + else if (ct->ct_flags & CT_UNION) { + PyErr_Format(PyExc_NotImplementedError, + "ctype '%s' not supported as %s by libffi. " + "Unions" SUPPORTED_IN_API_MODE, + ct->ct_name, place, place); + return NULL; + } + else { + char *extra = ""; + if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) + extra = " (the support for complex types inside libffi " + "is mostly missing at this point, so CFFI only " + "supports complex types as arguments or return " + "value in API-mode functions)"; + + PyErr_Format(PyExc_NotImplementedError, + "ctype '%s' (size %zd) not supported as %s%s", + ct->ct_name, ct->ct_size, place, extra); + return NULL; + } +} + +#define ALIGN_ARG(n) ((n) + 7) & ~7 + +static int fb_build(struct funcbuilder_s *fb, PyObject *fargs, + CTypeDescrObject *fresult) +{ + Py_ssize_t i, nargs = PyTuple_GET_SIZE(fargs); + Py_ssize_t exchange_offset; + cif_description_t *cif_descr; + + /* ffi buffer: start with a cif_description */ + cif_descr = fb_alloc(fb, sizeof(cif_description_t) + + nargs * sizeof(Py_ssize_t)); + + /* ffi buffer: next comes an array of 'ffi_type*', one per argument */ + fb->atypes = fb_alloc(fb, nargs * sizeof(ffi_type*)); + fb->nargs = nargs; + + /* ffi buffer: next comes the result type */ + fb->rtype = fb_fill_type(fb, fresult, 1); + if (PyErr_Occurred()) + return -1; + if (cif_descr != NULL) { + /* exchange data size */ + /* first, enough room for an array of 'nargs' pointers */ + exchange_offset = nargs * sizeof(void*); + exchange_offset = ALIGN_ARG(exchange_offset); + cif_descr->exchange_offset_arg[0] = exchange_offset; + /* then enough room for the result --- which means at least + sizeof(ffi_arg), according to the ffi docs */ + i = fb->rtype->size; + if (i < (Py_ssize_t)sizeof(ffi_arg)) + i = sizeof(ffi_arg); + exchange_offset += i; + } + else + exchange_offset = 0; /* not used */ + + /* loop over the arguments */ + for (i=0; ict_flags & CT_ARRAY) + farg = (CTypeDescrObject *)farg->ct_stuff; + + /* ffi buffer: fill in the ffi for the i'th argument */ + assert(farg != NULL); + atype = fb_fill_type(fb, farg, 0); + if (PyErr_Occurred()) + return -1; + + if (fb->atypes != NULL) { + fb->atypes[i] = atype; + /* exchange data size */ + exchange_offset = ALIGN_ARG(exchange_offset); + cif_descr->exchange_offset_arg[1 + i] = exchange_offset; + exchange_offset += atype->size; + } + } + + if (cif_descr != NULL) { + /* exchange data size */ + /* we also align it to the next multiple of 8, in an attempt to + work around bugs(?) of libffi like #241 */ + cif_descr->exchange_size = ALIGN_ARG(exchange_offset); + } + return 0; +} + +#undef ALIGN_ARG + +static void fb_cat_name(struct funcbuilder_s *fb, const char *piece, + int piecelen) +{ + if (fb->bufferp == NULL) { + fb->nb_bytes += piecelen; + } + else { + memcpy(fb->bufferp, piece, piecelen); + fb->bufferp += piecelen; + } +} + +static int fb_build_name(struct funcbuilder_s *fb, const char *repl, + CTypeDescrObject **pfargs, Py_ssize_t nargs, + CTypeDescrObject *fresult, int ellipsis) +{ + Py_ssize_t i; + fb->nargs = nargs; + + /* name: the function type name we build here is, like in C, made + as follows: + + RESULT_TYPE_HEAD (*)(ARG_1_TYPE, ARG_2_TYPE, etc) RESULT_TYPE_TAIL + */ + fb_cat_name(fb, fresult->ct_name, fresult->ct_name_position); + if (repl[0] != '(' && + fresult->ct_name[fresult->ct_name_position - 1] != '*') + fb_cat_name(fb, " ", 1); /* add a space */ + fb_cat_name(fb, repl, strlen(repl)); + if (fb->fct) { + i = strlen(repl) - 1; /* between '(*' and ')' */ + assert(repl[i] == ')'); + fb->fct->ct_name_position = fresult->ct_name_position + i; + } + fb_cat_name(fb, "(", 1); + + /* loop over the arguments */ + for (i=0; i 0) + fb_cat_name(fb, ", ", 2); + fb_cat_name(fb, farg->ct_name, strlen(farg->ct_name)); + } + + /* name: add the '...' if needed */ + if (ellipsis) { + if (nargs > 0) + fb_cat_name(fb, ", ", 2); + fb_cat_name(fb, "...", 3); + } + + /* name: concatenate the tail of the result type */ + fb_cat_name(fb, ")", 1); + fb_cat_name(fb, fresult->ct_name + fresult->ct_name_position, + strlen(fresult->ct_name) - fresult->ct_name_position + 1); + return 0; +} + +static CTypeDescrObject *fb_prepare_ctype(struct funcbuilder_s *fb, + PyObject *fargs, + CTypeDescrObject *fresult, + int ellipsis, int fabi) +{ + CTypeDescrObject *fct, **pfargs; + Py_ssize_t nargs; + char *repl = "(*)"; + + fb->nb_bytes = 0; + fb->bufferp = NULL; + fb->fct = NULL; + + pfargs = (CTypeDescrObject **)&PyTuple_GET_ITEM(fargs, 0); + nargs = PyTuple_GET_SIZE(fargs); +#if defined(MS_WIN32) && !defined(_WIN64) + if (fabi == FFI_STDCALL) + repl = "(__stdcall *)"; +#endif + + /* compute the total size needed for the name */ + if (fb_build_name(fb, repl, pfargs, nargs, fresult, ellipsis) < 0) + return NULL; + + /* allocate the function type */ + fct = ctypedescr_new(fb->nb_bytes); + if (fct == NULL) + return NULL; + fb->fct = fct; + + /* call again fb_build_name() to really build the ct_name */ + fb->bufferp = fct->ct_name; + if (fb_build_name(fb, repl, pfargs, nargs, fresult, ellipsis) < 0) + goto error; + assert(fb->bufferp == fct->ct_name + fb->nb_bytes); + + fct->ct_extra = NULL; + fct->ct_size = sizeof(void(*)(void)); + fct->ct_flags = CT_FUNCTIONPTR; + return fct; + + error: + Py_DECREF(fct); + return NULL; +} + +static cif_description_t *fb_prepare_cif(PyObject *fargs, + CTypeDescrObject *fresult, + ffi_abi fabi) +{ + char *buffer; + cif_description_t *cif_descr; + struct funcbuilder_s funcbuffer; + + funcbuffer.nb_bytes = 0; + funcbuffer.bufferp = NULL; + + /* compute the total size needed in the buffer for libffi */ + if (fb_build(&funcbuffer, fargs, fresult) < 0) + return NULL; + + /* allocate the buffer */ + buffer = PyObject_Malloc(funcbuffer.nb_bytes); + if (buffer == NULL) { + PyErr_NoMemory(); + return NULL; + } + + /* call again fb_build() to really build the libffi data structures */ + funcbuffer.bufferp = buffer; + if (fb_build(&funcbuffer, fargs, fresult) < 0) + goto error; + assert(funcbuffer.bufferp == buffer + funcbuffer.nb_bytes); + + cif_descr = (cif_description_t *)buffer; + if (ffi_prep_cif(&cif_descr->cif, fabi, funcbuffer.nargs, + funcbuffer.rtype, funcbuffer.atypes) != FFI_OK) { + PyErr_SetString(PyExc_SystemError, + "libffi failed to build this function type"); + goto error; + } + return cif_descr; + + error: + PyObject_Free(buffer); + return NULL; +} + +static PyObject *new_function_type(PyObject *fargs, /* tuple */ + CTypeDescrObject *fresult, + int ellipsis, int fabi) +{ + PyObject *fabiobj; + CTypeDescrObject *fct; + struct funcbuilder_s funcbuilder; + Py_ssize_t i; + const void **unique_key; + + if ((fresult->ct_size < 0 && !(fresult->ct_flags & CT_VOID)) || + (fresult->ct_flags & CT_ARRAY)) { + char *msg; + if (fresult->ct_flags & CT_IS_OPAQUE) + msg = "result type '%s' is opaque"; + else + msg = "invalid result type: '%s'"; + PyErr_Format(PyExc_TypeError, msg, fresult->ct_name); + return NULL; + } + + fct = fb_prepare_ctype(&funcbuilder, fargs, fresult, ellipsis, fabi); + if (fct == NULL) + return NULL; + + if (!ellipsis) { + /* Functions with '...' varargs are stored without a cif_descr + at all. The cif is computed on every call from the actual + types passed in. For all other functions, the cif_descr + is computed here. */ + cif_description_t *cif_descr; + + cif_descr = fb_prepare_cif(fargs, fresult, fabi); + if (cif_descr == NULL) { + if (PyErr_ExceptionMatches(PyExc_NotImplementedError)) { + PyErr_Clear(); /* will get the exception if we see an + actual call */ + } + else + goto error; + } + + fct->ct_extra = (char *)cif_descr; + } + + /* build the signature, given by a tuple of ctype objects */ + fct->ct_stuff = PyTuple_New(2 + funcbuilder.nargs); + if (fct->ct_stuff == NULL) + goto error; + fabiobj = PyInt_FromLong(fabi); + if (fabiobj == NULL) + goto error; + PyTuple_SET_ITEM(fct->ct_stuff, 0, fabiobj); + + Py_INCREF(fresult); + PyTuple_SET_ITEM(fct->ct_stuff, 1, (PyObject *)fresult); + for (i=0; ict_flags & CT_ARRAY) + o = ((CTypeDescrObject *)o)->ct_stuff; + Py_INCREF(o); + PyTuple_SET_ITEM(fct->ct_stuff, 2 + i, o); + } + + /* [ctresult, ellipsis+abi, num_args, ctargs...] */ + unique_key = alloca((3 + funcbuilder.nargs) * sizeof(void *)); + unique_key[0] = fresult; + unique_key[1] = (const void *)(Py_ssize_t)((fabi << 1) | !!ellipsis); + unique_key[2] = (const void *)(Py_ssize_t)(funcbuilder.nargs); + for (i=0; ict_stuff, 2 + i); + return get_unique_type(fct, unique_key, 3 + funcbuilder.nargs); + + error: + Py_DECREF(fct); + return NULL; +} + +static PyObject *b_new_function_type(PyObject *self, PyObject *args) +{ + PyObject *fargs; + CTypeDescrObject *fresult; + int ellipsis = 0, fabi = FFI_DEFAULT_ABI; + + if (!PyArg_ParseTuple(args, "O!O!|ii:new_function_type", + &PyTuple_Type, &fargs, + &CTypeDescr_Type, &fresult, + &ellipsis, + &fabi)) + return NULL; + + return new_function_type(fargs, fresult, ellipsis, fabi); +} + +static int convert_from_object_fficallback(char *result, + CTypeDescrObject *ctype, + PyObject *pyobj, + int encode_result_for_libffi) +{ + /* work work work around a libffi irregularity: for integer return + types we have to fill at least a complete 'ffi_arg'-sized result + buffer. */ + if (ctype->ct_size < (Py_ssize_t)sizeof(ffi_arg)) { + if (ctype->ct_flags & CT_VOID) { + if (pyobj == Py_None) { + return 0; + } + else { + PyErr_SetString(PyExc_TypeError, + "callback with the return type 'void' must return None"); + return -1; + } + } + if (!encode_result_for_libffi) + goto skip; + if (ctype->ct_flags & CT_PRIMITIVE_SIGNED) { + PY_LONG_LONG value; + /* It's probably fine to always zero-extend, but you never + know: maybe some code somewhere expects a negative + 'short' result to be returned into EAX as a 32-bit + negative number. Better safe than sorry. This code + is about that case. Let's ignore this for enums. + */ + /* do a first conversion only to detect overflows. This + conversion produces stuff that is otherwise ignored. */ + if (convert_from_object(result, ctype, pyobj) < 0) + return -1; + /* manual inlining and tweaking of convert_from_object() + in order to write a whole 'ffi_arg'. */ + value = _my_PyLong_AsLongLong(pyobj); + if (value == -1 && PyErr_Occurred()) + return -1; + write_raw_integer_data(result, value, sizeof(ffi_arg)); + return 0; + } + else if (ctype->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED | + CT_POINTER | CT_FUNCTIONPTR)) { + /* zero extension: fill the '*result' with zeros, and (on big- + endian machines) correct the 'result' pointer to write to. + We also do that for pointers, even though we're normally not + in this branch because ctype->ct_size == sizeof(ffi_arg) for + pointers---except on some architectures like x32 (issue #372). + */ + memset(result, 0, sizeof(ffi_arg)); +#ifdef WORDS_BIGENDIAN + result += (sizeof(ffi_arg) - ctype->ct_size); +#endif + } + } + skip: + return convert_from_object(result, ctype, pyobj); +} + +static void _my_PyErr_WriteUnraisable(PyObject *t, PyObject *v, PyObject *tb, + char *objdescr, PyObject *obj, + char *extra_error_line) +{ + /* like PyErr_WriteUnraisable(), but write a full traceback */ + PyObject *f; +#if PY_MAJOR_VERSION >= 3 + /* jump through hoops to ensure the tb is attached to v, on Python 3 */ + PyErr_NormalizeException(&t, &v, &tb); + if (tb == NULL) { + tb = Py_None; + Py_INCREF(tb); + } + PyException_SetTraceback(v, tb); +#endif + f = PySys_GetObject("stderr"); + if (f != NULL) { + if (obj != NULL) { + PyFile_WriteString(objdescr, f); + PyFile_WriteObject(obj, f, 0); + PyFile_WriteString(":\n", f); + } + if (extra_error_line != NULL) + PyFile_WriteString(extra_error_line, f); + PyErr_Display(t, v, tb); + } + Py_XDECREF(t); + Py_XDECREF(v); + Py_XDECREF(tb); +} + +static void general_invoke_callback(int decode_args_from_libffi, + void *result, char *args, void *userdata) +{ + PyObject *cb_args = (PyObject *)userdata; + CTypeDescrObject *ct = (CTypeDescrObject *)PyTuple_GET_ITEM(cb_args, 0); + PyObject *signature = ct->ct_stuff; + PyObject *py_ob = PyTuple_GET_ITEM(cb_args, 1); + PyObject *py_args = NULL; + PyObject *py_res = NULL; + PyObject *py_rawerr; + PyObject *onerror_cb; + Py_ssize_t i, n; + char *extra_error_line = NULL; + +#define SIGNATURE(i) ((CTypeDescrObject *)PyTuple_GET_ITEM(signature, i)) + + Py_INCREF(cb_args); + + n = PyTuple_GET_SIZE(signature) - 2; + py_args = PyTuple_New(n); + if (py_args == NULL) + goto error; + + for (i=0; ict_flags & (CT_IS_LONGDOUBLE | CT_STRUCT | CT_UNION)) + a_src = *(char **)a_src; + } + a = convert_to_object(a_src, a_ct); + if (a == NULL) + goto error; + PyTuple_SET_ITEM(py_args, i, a); + } + + py_res = PyObject_Call(py_ob, py_args, NULL); + if (py_res == NULL) + goto error; + if (convert_from_object_fficallback(result, SIGNATURE(1), py_res, + decode_args_from_libffi) < 0) { + extra_error_line = "Trying to convert the result back to C:\n"; + goto error; + } + done: + Py_XDECREF(py_args); + Py_XDECREF(py_res); + Py_DECREF(cb_args); + return; + + error: + if (SIGNATURE(1)->ct_size > 0) { + py_rawerr = PyTuple_GET_ITEM(cb_args, 2); + memcpy(result, PyBytes_AS_STRING(py_rawerr), + PyBytes_GET_SIZE(py_rawerr)); + } + onerror_cb = PyTuple_GET_ITEM(cb_args, 3); + if (onerror_cb == Py_None) { + PyObject *ecap, *t, *v, *tb; + PyErr_Fetch(&t, &v, &tb); + ecap = _cffi_start_error_capture(); + _my_PyErr_WriteUnraisable(t, v, tb, "From cffi callback ", py_ob, + extra_error_line); + _cffi_stop_error_capture(ecap); + } + else { + PyObject *exc1, *val1, *tb1, *res1, *exc2, *val2, *tb2; + PyErr_Fetch(&exc1, &val1, &tb1); + PyErr_NormalizeException(&exc1, &val1, &tb1); + res1 = PyObject_CallFunctionObjArgs(onerror_cb, + exc1 ? exc1 : Py_None, + val1 ? val1 : Py_None, + tb1 ? tb1 : Py_None, + NULL); + if (res1 != NULL) { + if (res1 != Py_None) + convert_from_object_fficallback(result, SIGNATURE(1), res1, + decode_args_from_libffi); + Py_DECREF(res1); + } + if (!PyErr_Occurred()) { + Py_XDECREF(exc1); + Py_XDECREF(val1); + Py_XDECREF(tb1); + } + else { + /* double exception! print a double-traceback... */ + PyObject *ecap; + PyErr_Fetch(&exc2, &val2, &tb2); + ecap = _cffi_start_error_capture(); + _my_PyErr_WriteUnraisable(exc1, val1, tb1, + "From cffi callback ", py_ob, + extra_error_line); + extra_error_line = ("\nDuring the call to 'onerror', " + "another exception occurred:\n\n"); + _my_PyErr_WriteUnraisable(exc2, val2, tb2, + NULL, NULL, extra_error_line); + _cffi_stop_error_capture(ecap); + } + } + goto done; + +#undef SIGNATURE +} + +static void invoke_callback(ffi_cif *cif, void *result, void **args, + void *userdata) +{ + save_errno(); + { + PyGILState_STATE state = gil_ensure(); + general_invoke_callback(1, result, (char *)args, userdata); + gil_release(state); + } + restore_errno(); +} + +static PyObject *prepare_callback_info_tuple(CTypeDescrObject *ct, + PyObject *ob, + PyObject *error_ob, + PyObject *onerror_ob, + int decode_args_from_libffi) +{ + CTypeDescrObject *ctresult; + PyObject *py_rawerr, *infotuple; + Py_ssize_t size; + + if (!(ct->ct_flags & CT_FUNCTIONPTR)) { + PyErr_Format(PyExc_TypeError, "expected a function ctype, got '%s'", + ct->ct_name); + return NULL; + } + if (!PyCallable_Check(ob)) { + PyErr_Format(PyExc_TypeError, + "expected a callable object, not %.200s", + Py_TYPE(ob)->tp_name); + return NULL; + } + if (onerror_ob != Py_None && !PyCallable_Check(onerror_ob)) { + PyErr_Format(PyExc_TypeError, + "expected a callable object for 'onerror', not %.200s", + Py_TYPE(onerror_ob)->tp_name); + return NULL; + } + + ctresult = (CTypeDescrObject *)PyTuple_GET_ITEM(ct->ct_stuff, 1); + size = ctresult->ct_size; + if (size < (Py_ssize_t)sizeof(ffi_arg)) + size = sizeof(ffi_arg); + py_rawerr = PyBytes_FromStringAndSize(NULL, size); + if (py_rawerr == NULL) + return NULL; + memset(PyBytes_AS_STRING(py_rawerr), 0, size); + if (error_ob != Py_None) { + if (convert_from_object_fficallback( + PyBytes_AS_STRING(py_rawerr), ctresult, error_ob, + decode_args_from_libffi) < 0) { + Py_DECREF(py_rawerr); + return NULL; + } + } + infotuple = Py_BuildValue("OOOO", ct, ob, py_rawerr, onerror_ob); + Py_DECREF(py_rawerr); + +#ifdef WITH_THREAD + /* We must setup the GIL here, in case the callback is invoked in + some other non-Pythonic thread. This is the same as ctypes. */ + PyEval_InitThreads(); +#endif + + return infotuple; +} + +static PyObject *b_callback(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + CDataObject_closure *cd; + PyObject *ob, *error_ob = Py_None, *onerror_ob = Py_None; + PyObject *infotuple; + cif_description_t *cif_descr; + ffi_closure *closure; + void *closure_exec; + + if (!PyArg_ParseTuple(args, "O!O|OO:callback", &CTypeDescr_Type, &ct, &ob, + &error_ob, &onerror_ob)) + return NULL; + + infotuple = prepare_callback_info_tuple(ct, ob, error_ob, onerror_ob, 1); + if (infotuple == NULL) + return NULL; + +#ifdef CFFI_TRUST_LIBFFI + closure = ffi_closure_alloc(sizeof(ffi_closure), &closure_exec); +#else + closure = cffi_closure_alloc(); + closure_exec = closure; +#endif + if (closure == NULL) { + Py_DECREF(infotuple); + return NULL; + } + cd = PyObject_GC_New(CDataObject_closure, &CDataOwningGC_Type); + if (cd == NULL) + goto error; + Py_INCREF(ct); + cd->head.c_type = ct; + cd->head.c_data = (char *)closure_exec; + cd->head.c_weakreflist = NULL; + cd->closure = closure; + PyObject_GC_Track(cd); + + cif_descr = (cif_description_t *)ct->ct_extra; + if (cif_descr == NULL) { + PyErr_Format(PyExc_NotImplementedError, + "%s: callback with unsupported argument or " + "return type or with '...'", ct->ct_name); + goto error; + } +#ifdef CFFI_TRUST_LIBFFI + if (ffi_prep_closure_loc(closure, &cif_descr->cif, + invoke_callback, infotuple, closure_exec) != FFI_OK) { +#else + if (ffi_prep_closure(closure, &cif_descr->cif, + invoke_callback, infotuple) != FFI_OK) { +#endif + PyErr_SetString(PyExc_SystemError, + "libffi failed to build this callback"); + goto error; + } + if (closure->user_data != infotuple) { + /* Issue #266. Should not occur, but could, if we are using + at runtime a version of libffi compiled with a different + 'ffi_closure' structure than the one we expect from ffi.h + (e.g. difference in details of the platform): a difference + in FFI_TRAMPOLINE_SIZE means that the 'user_data' field + ends up somewhere else, and so the test above fails. + */ + PyErr_SetString(PyExc_SystemError, + "ffi_prep_closure(): bad user_data (it seems that the " + "version of the libffi library seen at runtime is " + "different from the 'ffi.h' file seen at compile-time)"); + goto error; + } + return (PyObject *)cd; + + error: + closure->user_data = NULL; + if (cd == NULL) { +#ifdef CFFI_TRUST_LIBFFI + ffi_closure_free(closure); +#else + cffi_closure_free(closure); +#endif + } + else + Py_DECREF(cd); + Py_XDECREF(infotuple); + return NULL; +} + +static PyObject *b_new_enum_type(PyObject *self, PyObject *args) +{ + char *ename; + PyObject *enumerators, *enumvalues; + PyObject *dict1 = NULL, *dict2 = NULL, *combined = NULL, *tmpkey = NULL; + int name_size; + CTypeDescrObject *td, *basetd; + Py_ssize_t i, n; + + if (!PyArg_ParseTuple(args, "sO!O!O!:new_enum_type", + &ename, + &PyTuple_Type, &enumerators, + &PyTuple_Type, &enumvalues, + &CTypeDescr_Type, &basetd)) + return NULL; + + n = PyTuple_GET_SIZE(enumerators); + if (n != PyTuple_GET_SIZE(enumvalues)) { + PyErr_SetString(PyExc_ValueError, + "tuple args must have the same size"); + return NULL; + } + + if (!(basetd->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_UNSIGNED))) { + PyErr_SetString(PyExc_TypeError, + "expected a primitive signed or unsigned base type"); + return NULL; + } + + dict1 = PyDict_New(); + if (dict1 == NULL) + goto error; + dict2 = PyDict_New(); + if (dict2 == NULL) + goto error; + + for (i=n; --i >= 0; ) { + long long lvalue; + PyObject *value = PyTuple_GET_ITEM(enumvalues, i); + tmpkey = PyTuple_GET_ITEM(enumerators, i); + Py_INCREF(tmpkey); + if (!PyText_Check(tmpkey)) { +#if PY_MAJOR_VERSION < 3 + if (PyUnicode_Check(tmpkey)) { + const char *text = PyText_AsUTF8(tmpkey); + if (text == NULL) + goto error; + Py_DECREF(tmpkey); + tmpkey = PyString_FromString(text); + if (tmpkey == NULL) + goto error; + } + else +#endif + { + PyErr_SetString(PyExc_TypeError, + "enumerators must be a list of strings"); + goto error; + } + } + if (convert_from_object((char*)&lvalue, basetd, value) < 0) + goto error; /* out-of-range or badly typed 'value' */ + if (PyDict_SetItem(dict1, tmpkey, value) < 0) + goto error; + if (PyDict_SetItem(dict2, value, tmpkey) < 0) + goto error; + Py_DECREF(tmpkey); + tmpkey = NULL; + } + + combined = PyTuple_Pack(2, dict1, dict2); + if (combined == NULL) + goto error; + + Py_CLEAR(dict2); + Py_CLEAR(dict1); + + name_size = strlen(ename) + 1; + td = ctypedescr_new(name_size); + if (td == NULL) + goto error; + + memcpy(td->ct_name, ename, name_size); + td->ct_stuff = combined; + td->ct_size = basetd->ct_size; + td->ct_length = basetd->ct_length; /* alignment */ + td->ct_extra = basetd->ct_extra; /* ffi type */ + td->ct_flags = basetd->ct_flags | CT_IS_ENUM; + td->ct_name_position = name_size - 1; + return (PyObject *)td; + + error: + Py_XDECREF(tmpkey); + Py_XDECREF(combined); + Py_XDECREF(dict2); + Py_XDECREF(dict1); + return NULL; +} + +static PyObject *b_alignof(PyObject *self, PyObject *arg) +{ + int align; + if (!CTypeDescr_Check(arg)) { + PyErr_SetString(PyExc_TypeError, "expected a 'ctype' object"); + return NULL; + } + align = get_alignment((CTypeDescrObject *)arg); + if (align < 0) + return NULL; + return PyInt_FromLong(align); +} + +static Py_ssize_t direct_sizeof_cdata(CDataObject *cd) +{ + Py_ssize_t size; + if (cd->c_type->ct_flags & CT_ARRAY) + size = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size; + else { + size = -1; + if (cd->c_type->ct_flags & (CT_STRUCT | CT_UNION)) + size = _cdata_var_byte_size(cd); + if (size < 0) + size = cd->c_type->ct_size; + } + return size; +} + +static PyObject *b_sizeof(PyObject *self, PyObject *arg) +{ + Py_ssize_t size; + + if (CData_Check(arg)) { + size = direct_sizeof_cdata((CDataObject *)arg); + } + else if (CTypeDescr_Check(arg)) { + size = ((CTypeDescrObject *)arg)->ct_size; + if (size < 0) { + PyErr_Format(PyExc_ValueError, "ctype '%s' is of unknown size", + ((CTypeDescrObject *)arg)->ct_name); + return NULL; + } + } + else { + PyErr_SetString(PyExc_TypeError, + "expected a 'cdata' or 'ctype' object"); + return NULL; + } + return PyInt_FromSsize_t(size); +} + +static PyObject *b_typeof(PyObject *self, PyObject *arg) +{ + PyObject *res; + + if (!CData_Check(arg)) { + PyErr_SetString(PyExc_TypeError, "expected a 'cdata' object"); + return NULL; + } + res = (PyObject *)((CDataObject *)arg)->c_type; + Py_INCREF(res); + return res; +} + +static CTypeDescrObject *direct_typeoffsetof(CTypeDescrObject *ct, + PyObject *fieldname, + int following, Py_ssize_t *offset) +{ + /* Does not return a new reference! */ + CTypeDescrObject *res; + CFieldObject *cf; + + if (PyTextAny_Check(fieldname)) { + if (!following && (ct->ct_flags & CT_POINTER)) + ct = ct->ct_itemdescr; + if (!(ct->ct_flags & (CT_STRUCT|CT_UNION))) { + PyErr_SetString(PyExc_TypeError, + "with a field name argument, expected a " + "struct or union ctype"); + return NULL; + } + if (force_lazy_struct(ct) <= 0) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_TypeError, "struct/union is opaque"); + return NULL; + } + cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, fieldname); + if (cf == NULL) { + PyErr_SetObject(PyExc_KeyError, fieldname); + return NULL; + } + if (cf->cf_bitshift >= 0) { + PyErr_SetString(PyExc_TypeError, "not supported for bitfields"); + return NULL; + } + res = cf->cf_type; + *offset = cf->cf_offset; + } + else { + Py_ssize_t index = PyInt_AsSsize_t(fieldname); + if (index < 0 && PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "field name or array index expected"); + return NULL; + } + + if (!(ct->ct_flags & (CT_ARRAY|CT_POINTER)) || + ct->ct_itemdescr->ct_size < 0) { + PyErr_SetString(PyExc_TypeError, "with an integer argument, " + "expected an array ctype or a " + "pointer to non-opaque"); + return NULL; + } + res = ct->ct_itemdescr; + *offset = MUL_WRAPAROUND(index, ct->ct_itemdescr->ct_size); + if ((*offset / ct->ct_itemdescr->ct_size) != index) { + PyErr_SetString(PyExc_OverflowError, + "array offset would overflow a Py_ssize_t"); + return NULL; + } + } + return res; +} + +static PyObject *b_typeoffsetof(PyObject *self, PyObject *args) +{ + PyObject *res, *fieldname; + CTypeDescrObject *ct; + Py_ssize_t offset; + int following = 0; + + if (!PyArg_ParseTuple(args, "O!O|i:typeoffsetof", + &CTypeDescr_Type, &ct, &fieldname, &following)) + return NULL; + + res = (PyObject *)direct_typeoffsetof(ct, fieldname, following, &offset); + if (res == NULL) + return NULL; + + return Py_BuildValue("(On)", res, offset); +} + +static PyObject *b_rawaddressof(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + CDataObject *cd; + Py_ssize_t offset; + int accepted_flags; + + if (!PyArg_ParseTuple(args, "O!O!n:rawaddressof", + &CTypeDescr_Type, &ct, + &CData_Type, &cd, + &offset)) + return NULL; + + accepted_flags = CT_STRUCT | CT_UNION | CT_ARRAY | CT_POINTER; + if ((cd->c_type->ct_flags & accepted_flags) == 0) { + PyErr_SetString(PyExc_TypeError, + "expected a cdata struct/union/array/pointer object"); + return NULL; + } + if ((ct->ct_flags & CT_POINTER) == 0) { + PyErr_SetString(PyExc_TypeError, + "expected a pointer ctype"); + return NULL; + } + return new_simple_cdata(cd->c_data + offset, ct); +} + +static PyObject *b_getcname(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + char *replace_with, *p, *s; + Py_ssize_t namelen, replacelen; + + if (!PyArg_ParseTuple(args, "O!s:getcname", + &CTypeDescr_Type, &ct, &replace_with)) + return NULL; + + namelen = strlen(ct->ct_name); + replacelen = strlen(replace_with); + s = p = alloca(namelen + replacelen + 1); + memcpy(p, ct->ct_name, ct->ct_name_position); + p += ct->ct_name_position; + memcpy(p, replace_with, replacelen); + p += replacelen; + memcpy(p, ct->ct_name + ct->ct_name_position, + namelen - ct->ct_name_position); + + return PyText_FromStringAndSize(s, namelen + replacelen); +} + +static PyObject *b_string(PyObject *self, PyObject *args, PyObject *kwds) +{ + CDataObject *cd; + Py_ssize_t maxlen = -1; + static char *keywords[] = {"cdata", "maxlen", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|n:string", keywords, + &CData_Type, &cd, &maxlen)) + return NULL; + + if (cd->c_type->ct_itemdescr != NULL && + cd->c_type->ct_itemdescr->ct_flags & (CT_PRIMITIVE_CHAR | + CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED) && + !(cd->c_type->ct_itemdescr->ct_flags & CT_IS_BOOL)) { + Py_ssize_t length = maxlen; + if (cd->c_data == NULL) { + PyObject *s = cdata_repr(cd); + if (s != NULL) { + PyErr_Format(PyExc_RuntimeError, + "cannot use string() on %s", + PyText_AS_UTF8(s)); + Py_DECREF(s); + } + return NULL; + } + if (length < 0 && cd->c_type->ct_flags & CT_ARRAY) { + length = get_array_length(cd); + } + if (cd->c_type->ct_itemdescr->ct_size == sizeof(char)) { + const char *start = cd->c_data; + if (length < 0) { + /*READ(start, 1)*/ + length = strlen(start); + /*READ(start, length)*/ + } + else { + const char *end; + /*READ(start, length)*/ + end = (const char *)memchr(start, 0, length); + if (end != NULL) + length = end - start; + } + return PyBytes_FromStringAndSize(start, length); + } + else if (cd->c_type->ct_itemdescr->ct_flags & CT_PRIMITIVE_CHAR) { + switch (cd->c_type->ct_itemdescr->ct_size) { + case 2: { + const cffi_char16_t *start = (cffi_char16_t *)cd->c_data; + if (length < 0) { + /*READ(start, 2)*/ + length = 0; + while (start[length]) + length++; + /*READ(start, 2 * length)*/ + } + else { + /*READ(start, 2 * length)*/ + maxlen = length; + length = 0; + while (length < maxlen && start[length]) + length++; + } + return _my_PyUnicode_FromChar16(start, length); + } + case 4: { + const cffi_char32_t *start = (cffi_char32_t *)cd->c_data; + if (length < 0) { + /*READ(start, 4)*/ + length = 0; + while (start[length]) + length++; + /*READ(start, 4 * length)*/ + } + else { + /*READ(start, 4 * length)*/ + maxlen = length; + length = 0; + while (length < maxlen && start[length]) + length++; + } + return _my_PyUnicode_FromChar32(start, length); + } + } + } + } + else if (cd->c_type->ct_flags & CT_IS_ENUM) { + return convert_cdata_to_enum_string(cd, 0); + } + else if (cd->c_type->ct_flags & CT_IS_BOOL) { + /* fall through to TypeError */ + } + else if (cd->c_type->ct_flags & (CT_PRIMITIVE_CHAR | + CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED)) { + /*READ(cd->c_data, cd->c_type->ct_size)*/ + if (cd->c_type->ct_size == sizeof(char)) + return PyBytes_FromStringAndSize(cd->c_data, 1); + else if (cd->c_type->ct_flags & CT_PRIMITIVE_CHAR) { + switch (cd->c_type->ct_size) { + case 2: + return _my_PyUnicode_FromChar16((cffi_char16_t *)cd->c_data, 1); + case 4: + return _my_PyUnicode_FromChar32((cffi_char32_t *)cd->c_data, 1); + } + } + } + PyErr_Format(PyExc_TypeError, "string(): unexpected cdata '%s' argument", + cd->c_type->ct_name); + return NULL; +} + +static PyObject *b_unpack(PyObject *self, PyObject *args, PyObject *kwds) +{ + CDataObject *cd; + CTypeDescrObject *ctitem; + Py_ssize_t i, length, itemsize; + PyObject *result; + char *src; + int casenum; + static char *keywords[] = {"cdata", "length", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!n:unpack", keywords, + &CData_Type, &cd, &length)) + return NULL; + + if (!(cd->c_type->ct_flags & (CT_ARRAY|CT_POINTER))) { + PyErr_Format(PyExc_TypeError, + "expected a pointer or array, got '%s'", + cd->c_type->ct_name); + return NULL; + } + if (length < 0) { + PyErr_SetString(PyExc_ValueError, "'length' cannot be negative"); + return NULL; + } + if (cd->c_data == NULL) { + PyObject *s = cdata_repr(cd); + if (s != NULL) { + PyErr_Format(PyExc_RuntimeError, + "cannot use unpack() on %s", + PyText_AS_UTF8(s)); + Py_DECREF(s); + } + return NULL; + } + + /* byte- and unicode strings */ + ctitem = cd->c_type->ct_itemdescr; + if (ctitem->ct_flags & CT_PRIMITIVE_CHAR) { + switch (ctitem->ct_size) { + case sizeof(char): + return PyBytes_FromStringAndSize(cd->c_data, length); + case 2: + return _my_PyUnicode_FromChar16((cffi_char16_t *)cd->c_data,length); + case 4: + return _my_PyUnicode_FromChar32((cffi_char32_t *)cd->c_data,length); + } + } + + /* else, the result is a list. This implementation should be + equivalent to but much faster than '[p[i] for i in range(length)]'. + (Note that on PyPy, 'list(p[0:length])' should be equally fast, + but arguably, finding out that there *is* such an unexpected way + to write things down is the real problem.) + */ + result = PyList_New(length); + if (result == NULL) + return NULL; + + src = cd->c_data; + itemsize = ctitem->ct_size; + if (itemsize < 0) { + Py_DECREF(result); + PyErr_Format(PyExc_ValueError, "'%s' points to items of unknown size", + cd->c_type->ct_name); + return NULL; + } + + /* Determine some common fast-paths for the loop below. The case -1 + is the fall-back, which always gives the right answer. */ + +#define ALIGNMENT_CHECK(align) \ + (((align) & ((align) - 1)) == 0 && \ + (((uintptr_t)src) & ((align) - 1)) == 0) + + casenum = -1; + + if ((ctitem->ct_flags & CT_PRIMITIVE_ANY) && + ALIGNMENT_CHECK(ctitem->ct_length)) { + /* Source data is fully aligned; we can directly read without + memcpy(). The unaligned case is expected to be rare; in + this situation it is ok to fall back to the general + convert_to_object() in the loop. For now we also use this + fall-back for types that are too large. + */ + if (ctitem->ct_flags & CT_PRIMITIVE_SIGNED) { + if (itemsize == sizeof(long)) casenum = 3; + else if (itemsize == sizeof(int)) casenum = 2; + else if (itemsize == sizeof(short)) casenum = 1; + else if (itemsize == sizeof(signed char)) casenum = 0; + } + else if (ctitem->ct_flags & CT_PRIMITIVE_UNSIGNED) { + /* Note: we never pick case 6 if sizeof(int) == sizeof(long), + so that case 6 below can assume that the 'unsigned int' result + would always fit in a 'signed long'. */ + if (ctitem->ct_flags & CT_IS_BOOL) casenum = 11; + else if (itemsize == sizeof(unsigned long)) casenum = 7; + else if (itemsize == sizeof(unsigned int)) casenum = 6; + else if (itemsize == sizeof(unsigned short)) casenum = 5; + else if (itemsize == sizeof(unsigned char)) casenum = 4; + } + else if (ctitem->ct_flags & CT_PRIMITIVE_FLOAT) { + if (itemsize == sizeof(double)) casenum = 9; + else if (itemsize == sizeof(float)) casenum = 8; + } + } + else if (ctitem->ct_flags & (CT_POINTER | CT_FUNCTIONPTR)) { + casenum = 10; /* any pointer */ + } +#undef ALIGNMENT_CHECK + + for (i = 0; i < length; i++) { + PyObject *x; + switch (casenum) { + /* general case */ + default: x = convert_to_object(src, ctitem); break; + + /* special cases for performance only */ + case 0: x = PyInt_FromLong(*(signed char *)src); break; + case 1: x = PyInt_FromLong(*(short *)src); break; + case 2: x = PyInt_FromLong(*(int *)src); break; + case 3: x = PyInt_FromLong(*(long *)src); break; + case 4: x = PyInt_FromLong(*(unsigned char *)src); break; + case 5: x = PyInt_FromLong(*(unsigned short *)src); break; + case 6: x = PyInt_FromLong((long)*(unsigned int *)src); break; + case 7: x = PyLong_FromUnsignedLong(*(unsigned long *)src); break; + case 8: x = PyFloat_FromDouble(*(float *)src); break; + case 9: x = PyFloat_FromDouble(*(double *)src); break; + case 10: x = new_simple_cdata(*(char **)src, ctitem); break; + case 11: + switch (*(unsigned char *)src) { + case 0: x = Py_False; Py_INCREF(x); break; + case 1: x = Py_True; Py_INCREF(x); break; + default: x = convert_to_object(src, ctitem); /* error */ + } + break; + } + if (x == NULL) { + Py_DECREF(result); + return NULL; + } + PyList_SET_ITEM(result, i, x); + src += itemsize; + } + return result; +} + +static PyObject * +b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + /* this is the constructor of the type implemented in minibuffer.h */ + CDataObject *cd; + Py_ssize_t size = -1; + static char *keywords[] = {"cdata", "size", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|n:buffer", keywords, + &CData_Type, &cd, &size)) + return NULL; + + if (size < 0) + size = _cdata_var_byte_size(cd); + + if (cd->c_type->ct_flags & CT_POINTER) { + if (size < 0) + size = cd->c_type->ct_itemdescr->ct_size; + } + else if (cd->c_type->ct_flags & CT_ARRAY) { + if (size < 0) + size = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size; + } + else { + PyErr_Format(PyExc_TypeError, + "expected a pointer or array cdata, got '%s'", + cd->c_type->ct_name); + return NULL; + } + if (size < 0) { + PyErr_Format(PyExc_TypeError, + "don't know the size pointed to by '%s'", + cd->c_type->ct_name); + return NULL; + } + /*WRITE(cd->c_data, size)*/ + return minibuffer_new(cd->c_data, size, (PyObject *)cd); +} + +static PyObject *b_get_errno(PyObject *self, PyObject *noarg) +{ + int err; + restore_errno_only(); + err = errno; + errno = 0; + return PyInt_FromLong(err); +} + +static PyObject *b_set_errno(PyObject *self, PyObject *arg) +{ + long ival = PyInt_AsLong(arg); + if (ival == -1 && PyErr_Occurred()) + return NULL; + else if (ival < INT_MIN || ival > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, "errno value too large"); + return NULL; + } + errno = (int)ival; + save_errno_only(); + errno = 0; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *newp_handle(CTypeDescrObject *ct_voidp, PyObject *x) +{ + CDataObject_own_structptr *cd; + cd = (CDataObject_own_structptr *)PyObject_GC_New(CDataObject_own_structptr, + &CDataOwningGC_Type); + if (cd == NULL) + return NULL; + Py_INCREF(ct_voidp); + cd->head.c_type = ct_voidp; + cd->head.c_data = (char *)cd; + cd->head.c_weakreflist = NULL; + Py_INCREF(x); + cd->structobj = x; + PyObject_GC_Track(cd); + return (PyObject *)cd; +} + +static PyObject *b_newp_handle(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *x; + if (!PyArg_ParseTuple(args, "O!O", &CTypeDescr_Type, &ct, &x)) + return NULL; + + if (!(ct->ct_flags & CT_IS_VOID_PTR)) { + PyErr_Format(PyExc_TypeError, "needs 'void *', got '%s'", ct->ct_name); + return NULL; + } + + return newp_handle(ct, x); +} + +static PyObject *b_from_handle(PyObject *self, PyObject *arg) +{ + CTypeDescrObject *ct; + CDataObject_own_structptr *orgcd; + PyObject *x; + if (!CData_Check(arg)) { + PyErr_SetString(PyExc_TypeError, "expected a 'cdata' object"); + return NULL; + } + ct = ((CDataObject *)arg)->c_type; + if (!(ct->ct_flags & CT_IS_VOIDCHAR_PTR)) { + PyErr_Format(PyExc_TypeError, + "expected a 'cdata' object with a 'void *' out of " + "new_handle(), got '%s'", ct->ct_name); + return NULL; + } + orgcd = (CDataObject_own_structptr *)((CDataObject *)arg)->c_data; + if (!orgcd) { + PyErr_SetString(PyExc_RuntimeError, + "cannot use from_handle() on NULL pointer"); + return NULL; + } + if (Py_REFCNT(orgcd) <= 0 || Py_TYPE(orgcd) != &CDataOwningGC_Type) { + Py_FatalError("ffi.from_handle() detected that the address passed " + "points to garbage. If it is really the result of " + "ffi.new_handle(), then the Python object has already " + "been garbage collected"); + } + x = orgcd->structobj; + Py_INCREF(x); + return x; +} + +static int _my_PyObject_GetContiguousBuffer(PyObject *x, Py_buffer *view, + int writable_only) +{ +#if PY_MAJOR_VERSION < 3 + /* Some objects only support the buffer interface and CPython doesn't + translate it into the memoryview interface, mess. Hack a very + minimal content for 'view'. Don't care if the other fields are + uninitialized: we only call PyBuffer_Release(), which only reads + 'view->obj'. */ + PyBufferProcs *pb = x->ob_type->tp_as_buffer; + if (pb && !pb->bf_releasebuffer) { + /* we used to try all three in some vaguely sensible order, + i.e. first the write. But trying to call the write on a + read-only buffer fails with TypeError. So we use a less- + sensible order now. See test_from_buffer_more_cases. + + If 'writable_only', we only try bf_getwritebuffer. + */ + readbufferproc proc = NULL; + if (!writable_only) { + proc = (readbufferproc)pb->bf_getreadbuffer; + if (!proc) + proc = (readbufferproc)pb->bf_getcharbuffer; + } + if (!proc) + proc = (readbufferproc)pb->bf_getwritebuffer; + + if (proc && pb->bf_getsegcount) { + if ((*pb->bf_getsegcount)(x, NULL) != 1) { + PyErr_SetString(PyExc_TypeError, + "expected a single-segment buffer object"); + return -1; + } + view->len = (*proc)(x, 0, &view->buf); + if (view->len < 0) + return -1; + view->obj = x; + Py_INCREF(x); + return 0; + } + } +#endif + + if (PyObject_GetBuffer(x, view, writable_only ? PyBUF_WRITABLE + : PyBUF_SIMPLE) < 0) + return -1; + + if (!PyBuffer_IsContiguous(view, 'A')) { + PyBuffer_Release(view); + PyErr_SetString(PyExc_TypeError, "contiguous buffer expected"); + return -1; + } + return 0; +} + +static PyObject *direct_from_buffer(CTypeDescrObject *ct, PyObject *x, + int require_writable) +{ + CDataObject *cd; + Py_buffer *view; + Py_ssize_t arraylength; + + if (!(ct->ct_flags & CT_ARRAY)) { + PyErr_Format(PyExc_TypeError, "expected an array ctype, got '%s'", + ct->ct_name); + return NULL; + } + + /* PyPy 5.7 can obtain buffers for string (python 2) + or bytes (python 3). from_buffer(u"foo") is disallowed. + */ + if (PyUnicode_Check(x)) { + PyErr_SetString(PyExc_TypeError, + "from_buffer() cannot return the address " + "of a unicode object"); + return NULL; + } + + view = PyObject_Malloc(sizeof(Py_buffer)); + if (view == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (_my_PyObject_GetContiguousBuffer(x, view, require_writable) < 0) + goto error1; + + if (ct->ct_length >= 0) { + /* it's an array with a fixed length; make sure that the + buffer contains enough bytes. */ + if (view->len < ct->ct_size) { + PyErr_Format(PyExc_ValueError, + "buffer is too small (%zd bytes) for '%s' (%zd bytes)", + view->len, ct->ct_name, ct->ct_size); + goto error2; + } + arraylength = ct->ct_length; + } + else { + /* it's an open 'array[]' */ + if (ct->ct_itemdescr->ct_size == 1) { + /* fast path, performance only */ + arraylength = view->len; + } + else if (ct->ct_itemdescr->ct_size > 0) { + /* give it as many items as fit the buffer. Ignore a + partial last element. */ + arraylength = view->len / ct->ct_itemdescr->ct_size; + } + else { + /* it's an array 'empty[]'. Unsupported obscure case: + the problem is that setting the length of the result + to anything large (like SSIZE_T_MAX) is dangerous, + because if someone tries to loop over it, it will + turn effectively into an infinite loop. */ + PyErr_Format(PyExc_ZeroDivisionError, + "from_buffer('%s', ..): the actual length of the array " + "cannot be computed", ct->ct_name); + goto error2; + } + } + + cd = (CDataObject *)PyObject_GC_New(CDataObject_owngc_frombuf, + &CDataOwningGC_Type); + if (cd == NULL) + goto error2; + + Py_INCREF(ct); + cd->c_type = ct; + cd->c_data = view->buf; + cd->c_weakreflist = NULL; + ((CDataObject_owngc_frombuf *)cd)->length = arraylength; + ((CDataObject_owngc_frombuf *)cd)->bufferview = view; + PyObject_GC_Track(cd); + return (PyObject *)cd; + + error2: + PyBuffer_Release(view); + error1: + PyObject_Free(view); + return NULL; +} + +static PyObject *b_from_buffer(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *x; + int require_writable = 0; + + if (!PyArg_ParseTuple(args, "O!O|i", &CTypeDescr_Type, &ct, &x, + &require_writable)) + return NULL; + + return direct_from_buffer(ct, x, require_writable); +} + +static int _fetch_as_buffer(PyObject *x, Py_buffer *view, int writable_only) +{ + if (CData_Check(x)) { + CTypeDescrObject *ct = ((CDataObject *)x)->c_type; + if (!(ct->ct_flags & (CT_POINTER|CT_ARRAY))) { + PyErr_Format(PyExc_TypeError, + "expected a pointer or array ctype, got '%s'", + ct->ct_name); + return -1; + } + view->buf = ((CDataObject *)x)->c_data; + view->obj = NULL; + return 0; + } + else { + return _my_PyObject_GetContiguousBuffer(x, view, writable_only); + } +} + +static PyObject *b_memmove(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *dest_obj, *src_obj; + Py_buffer dest_view, src_view; + Py_ssize_t n; + static char *keywords[] = {"dest", "src", "n", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOn", keywords, + &dest_obj, &src_obj, &n)) + return NULL; + if (n < 0) { + PyErr_SetString(PyExc_ValueError, "negative size"); + return NULL; + } + + if (_fetch_as_buffer(src_obj, &src_view, 0) < 0) { + return NULL; + } + if (_fetch_as_buffer(dest_obj, &dest_view, 1) < 0) { + PyBuffer_Release(&src_view); + return NULL; + } + + memmove(dest_view.buf, src_view.buf, n); + + PyBuffer_Release(&dest_view); + PyBuffer_Release(&src_view); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *b__get_types(PyObject *self, PyObject *noarg) +{ + return PyTuple_Pack(2, (PyObject *)&CData_Type, + (PyObject *)&CTypeDescr_Type); +} + +/* forward, in commontypes.c */ +static PyObject *b__get_common_types(PyObject *self, PyObject *arg); + +static PyObject *b_gcp(PyObject *self, PyObject *args, PyObject *kwds) +{ + CDataObject *cd; + CDataObject *origobj; + PyObject *destructor; + Py_ssize_t ignored; /* for pypy */ + static char *keywords[] = {"cdata", "destructor", "size", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O|n:gc", keywords, + &CData_Type, &origobj, &destructor, + &ignored)) + return NULL; + + if (destructor == Py_None) { + if (!PyObject_TypeCheck(origobj, &CDataGCP_Type)) { + PyErr_SetString(PyExc_TypeError, + "Can remove destructor only on a object " + "previously returned by ffi.gc()"); + return NULL; + } + Py_CLEAR(((CDataObject_gcp *)origobj)->destructor); + Py_RETURN_NONE; + } + + cd = allocate_gcp_object(origobj, origobj->c_type, destructor); + return (PyObject *)cd; +} + +static PyObject *b_release(PyObject *self, PyObject *arg) +{ + if (!CData_Check(arg)) { + PyErr_SetString(PyExc_TypeError, "expected a 'cdata' object"); + return NULL; + } + return cdata_exit(arg, NULL); +} + +/************************************************************/ + +static char _testfunc0(char a, char b) +{ + return a + b; +} +static long _testfunc1(int a, long b) +{ + return (long)a + b; +} +static PY_LONG_LONG _testfunc2(PY_LONG_LONG a, PY_LONG_LONG b) +{ + return a + b; +} +static double _testfunc3(float a, double b) +{ + return a + b; +} +static float _testfunc4(float a, double b) +{ + return (float)(a + b); +} +static void _testfunc5(void) +{ + errno = errno + 15; +} +static int *_testfunc6(int *x) +{ + static int y; + y = *x - 1000; + return &y; +} +struct _testfunc7_s { unsigned char a1; short a2; }; +static short _testfunc7(struct _testfunc7_s inlined) +{ + return inlined.a1 + inlined.a2; +} +static int _testfunc9(int num, ...) +{ + va_list vargs; + int i, total = 0; + va_start(vargs, num); + for (i=0; ia1 + (int)ptr->a2; +} + +static long double _testfunc19(long double x, int count) +{ + int i; + for (i=0; ia1 + ptr->a2; +} + +struct _testfunc21_s { int a, b, c, d, e, f, g, h, i, j; }; +static int _testfunc21(struct _testfunc21_s inlined) +{ + return ((inlined.a << 0) + + (inlined.b << 1) + + (inlined.c << 2) + + (inlined.d << 3) + + (inlined.e << 4) + + (inlined.f << 5) + + (inlined.g << 6) + + (inlined.h << 7) + + (inlined.i << 8) + + (inlined.j << 9)); +} + +struct _testfunc22_s { int a[10]; }; +static struct _testfunc22_s _testfunc22(struct _testfunc22_s s1, + struct _testfunc22_s s2) +{ + struct _testfunc22_s result; + int i; + for (i=0; i<10; i++) + result.a[i] = s1.a[i] - s2.a[i]; + return result; +} + +static int _testfunc23(char *p) +{ + if (p) + return 1000 * p[0]; + return -42; +} + +#if 0 /* libffi doesn't properly support complexes currently */ + /* also, MSVC might not support _Complex... */ + /* if this is enabled one day, remember to also add _Complex + * arguments in addition to return values. */ +static float _Complex _testfunc24(float a, float b) +{ + return a + I*2.0*b; +} +static double _Complex _testfunc25(double a, double b) +{ + return a + I*2.0*b; +} +#endif + +static PyObject *b__testfunc(PyObject *self, PyObject *args) +{ + /* for testing only */ + int i; + void *f; + if (!PyArg_ParseTuple(args, "i:_testfunc", &i)) + return NULL; + switch (i) { + case 0: f = &_testfunc0; break; + case 1: f = &_testfunc1; break; + case 2: f = &_testfunc2; break; + case 3: f = &_testfunc3; break; + case 4: f = &_testfunc4; break; + case 5: f = &_testfunc5; break; + case 6: f = &_testfunc6; break; + case 7: f = &_testfunc7; break; + case 8: f = stderr; break; + case 9: f = &_testfunc9; break; + case 10: f = &_testfunc10; break; + case 11: f = &_testfunc11; break; + case 12: f = &_testfunc12; break; + case 13: f = &_testfunc13; break; + case 14: f = &_testfunc14; break; + case 15: f = &_testfunc15; break; + case 16: f = &_testfunc16; break; + case 17: f = &_testfunc17; break; + case 18: f = &_testfunc18; break; + case 19: f = &_testfunc19; break; + case 20: f = &_testfunc20; break; + case 21: f = &_testfunc21; break; + case 22: f = &_testfunc22; break; + case 23: f = &_testfunc23; break; +#if 0 + case 24: f = &_testfunc24; break; + case 25: f = &_testfunc25; break; +#endif + default: + PyErr_SetNone(PyExc_ValueError); + return NULL; + } + return PyLong_FromVoidPtr(f); +} + +#if PY_MAJOR_VERSION < 3 +static Py_ssize_t _test_segcountproc(PyObject *o, Py_ssize_t *ignored) +{ + return 1; +} +static Py_ssize_t _test_getreadbuf(PyObject *o, Py_ssize_t i, void **r) +{ + static char buf[] = "RDB"; + *r = buf; + return 3; +} +static Py_ssize_t _test_getwritebuf(PyObject *o, Py_ssize_t i, void **r) +{ + static char buf[] = "WRB"; + *r = buf; + return 3; +} +static Py_ssize_t _test_getcharbuf(PyObject *o, Py_ssize_t i, char **r) +{ + static char buf[] = "CHB"; + *r = buf; + return 3; +} +#endif +static int _test_getbuf(PyObject *self, Py_buffer *view, int flags) +{ + static char buf[] = "GTB"; + return PyBuffer_FillInfo(view, self, buf, 3, /*readonly=*/0, flags); +} +static int _test_getbuf_ro(PyObject *self, Py_buffer *view, int flags) +{ + static char buf[] = "ROB"; + return PyBuffer_FillInfo(view, self, buf, 3, /*readonly=*/1, flags); +} + + +static PyObject *b__testbuff(PyObject *self, PyObject *args) +{ + /* for testing only */ + int methods; + PyTypeObject *obj; + if (!PyArg_ParseTuple(args, "O!i|_testbuff", &PyType_Type, &obj, &methods)) + return NULL; + + assert(obj->tp_as_buffer != NULL); + +#if PY_MAJOR_VERSION < 3 + obj->tp_as_buffer->bf_getsegcount = &_test_segcountproc; + obj->tp_flags |= Py_TPFLAGS_HAVE_GETCHARBUFFER; + obj->tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER; + if (methods & 1) obj->tp_as_buffer->bf_getreadbuffer = &_test_getreadbuf; + if (methods & 2) obj->tp_as_buffer->bf_getwritebuffer = &_test_getwritebuf; + if (methods & 4) obj->tp_as_buffer->bf_getcharbuffer = &_test_getcharbuf; +#endif + if (methods & 8) obj->tp_as_buffer->bf_getbuffer = &_test_getbuf; + if (methods & 16) obj->tp_as_buffer->bf_getbuffer = &_test_getbuf_ro; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *b_init_cffi_1_0_external_module(PyObject *, PyObject *); +/* forward, see cffi1_module.c */ + + +static PyMethodDef FFIBackendMethods[] = { + {"load_library", b_load_library, METH_VARARGS}, + {"new_primitive_type", b_new_primitive_type, METH_VARARGS}, + {"new_pointer_type", b_new_pointer_type, METH_VARARGS}, + {"new_array_type", b_new_array_type, METH_VARARGS}, + {"new_void_type", b_new_void_type, METH_NOARGS}, + {"new_struct_type", b_new_struct_type, METH_VARARGS}, + {"new_union_type", b_new_union_type, METH_VARARGS}, + {"complete_struct_or_union", b_complete_struct_or_union, METH_VARARGS}, + {"new_function_type", b_new_function_type, METH_VARARGS}, + {"new_enum_type", b_new_enum_type, METH_VARARGS}, + {"newp", b_newp, METH_VARARGS}, + {"cast", b_cast, METH_VARARGS}, + {"callback", b_callback, METH_VARARGS}, + {"alignof", b_alignof, METH_O}, + {"sizeof", b_sizeof, METH_O}, + {"typeof", b_typeof, METH_O}, + {"typeoffsetof", b_typeoffsetof, METH_VARARGS}, + {"rawaddressof", b_rawaddressof, METH_VARARGS}, + {"getcname", b_getcname, METH_VARARGS}, + {"string", (PyCFunction)b_string, METH_VARARGS | METH_KEYWORDS}, + {"unpack", (PyCFunction)b_unpack, METH_VARARGS | METH_KEYWORDS}, + {"get_errno", b_get_errno, METH_NOARGS}, + {"set_errno", b_set_errno, METH_O}, + {"newp_handle", b_newp_handle, METH_VARARGS}, + {"from_handle", b_from_handle, METH_O}, + {"from_buffer", b_from_buffer, METH_VARARGS}, + {"memmove", (PyCFunction)b_memmove, METH_VARARGS | METH_KEYWORDS}, + {"gcp", (PyCFunction)b_gcp, METH_VARARGS | METH_KEYWORDS}, + {"release", b_release, METH_O}, +#ifdef MS_WIN32 + {"getwinerror", (PyCFunction)b_getwinerror, METH_VARARGS | METH_KEYWORDS}, +#endif + {"_get_types", b__get_types, METH_NOARGS}, + {"_get_common_types", b__get_common_types, METH_O}, + {"_testfunc", b__testfunc, METH_VARARGS}, + {"_testbuff", b__testbuff, METH_VARARGS}, + {"_init_cffi_1_0_external_module", b_init_cffi_1_0_external_module, METH_O}, + {NULL, NULL} /* Sentinel */ +}; + +/************************************************************/ +/* Functions used by '_cffi_N.so', the generated modules */ + +#define _cffi_to_c_SIGNED_FN(RETURNTYPE, SIZE) \ +static RETURNTYPE _cffi_to_c_i##SIZE(PyObject *obj) { \ + PY_LONG_LONG tmp = _my_PyLong_AsLongLong(obj); \ + if ((tmp > (PY_LONG_LONG)((1ULL<<(SIZE-1)) - 1)) || \ + (tmp < (PY_LONG_LONG)(0ULL-(1ULL<<(SIZE-1))))) \ + if (!PyErr_Occurred()) \ + return (RETURNTYPE)_convert_overflow(obj, #SIZE "-bit int"); \ + return (RETURNTYPE)tmp; \ +} + +#define _cffi_to_c_UNSIGNED_FN(RETURNTYPE, SIZE) \ +static RETURNTYPE _cffi_to_c_u##SIZE(PyObject *obj) { \ + unsigned PY_LONG_LONG tmp = _my_PyLong_AsUnsignedLongLong(obj, 1); \ + if (tmp > ~(((unsigned PY_LONG_LONG)-2) << (SIZE-1))) \ + if (!PyErr_Occurred()) \ + return (RETURNTYPE)_convert_overflow(obj, \ + #SIZE "-bit unsigned int"); \ + return (RETURNTYPE)tmp; \ +} + +_cffi_to_c_SIGNED_FN(int, 8) +_cffi_to_c_SIGNED_FN(int, 16) +_cffi_to_c_SIGNED_FN(int, 32) +_cffi_to_c_SIGNED_FN(PY_LONG_LONG, 64) +_cffi_to_c_UNSIGNED_FN(int, 8) +_cffi_to_c_UNSIGNED_FN(int, 16) +_cffi_to_c_UNSIGNED_FN(unsigned int, 32) +_cffi_to_c_UNSIGNED_FN(unsigned PY_LONG_LONG, 64) + +static PyObject *_cffi_from_c_pointer(char *ptr, CTypeDescrObject *ct) +{ + return convert_to_object((char *)&ptr, ct); +} + +static char *_cffi_to_c_pointer(PyObject *obj, CTypeDescrObject *ct) +{ + char *result; + if (convert_from_object((char *)&result, ct, obj) < 0) { + if ((ct->ct_flags & CT_POINTER) && + (ct->ct_itemdescr->ct_flags & CT_IS_FILE) && + PyFile_Check(obj)) { + PyErr_Clear(); + return (char *)PyFile_AsFile(obj); + } + return NULL; + } + return result; +} + +static long double _cffi_to_c_long_double(PyObject *obj) +{ + if (CData_Check(obj) && + (((CDataObject *)obj)->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + char *data = ((CDataObject *)obj)->c_data; + /*READ(data, sizeof(long double))*/ + return read_raw_longdouble_data(data); + } + else + return PyFloat_AsDouble(obj); +} + +static _Bool _cffi_to_c__Bool(PyObject *obj) +{ + PY_LONG_LONG tmp = _my_PyLong_AsLongLong(obj); + if (tmp == 0) + return 0; + else if (tmp == 1) + return 1; + else if (PyErr_Occurred()) + return (_Bool)-1; + else + return (_Bool)_convert_overflow(obj, "_Bool"); +} + +static PyObject *_cffi_get_struct_layout(Py_ssize_t nums[]) +{ + PyObject *result; + int count = 0; + while (nums[count] >= 0) + count++; + + result = PyList_New(count); + if (result == NULL) + return NULL; + + while (--count >= 0) { + PyObject *o = PyInt_FromSsize_t(nums[count]); + if (o == NULL) { + Py_DECREF(result); + return NULL; + } + PyList_SET_ITEM(result, count, o); + } + return result; +} + +static PyObject *_cffi_from_c_char(char x) { + return PyBytes_FromStringAndSize(&x, 1); +} + +/* backward-compatibility hack: instead of _cffi_to_c_char16_t() and + * _cffi_to_c_char32_t(), we have _cffi_to_c_wchar_t() handling whatever + * size is wchar_t, and _cffi_to_c_wchar3216_t() handling the opposite. + */ +#ifdef HAVE_WCHAR_H +typedef wchar_t cffi_wchar_t; +#else +typedef uint16_t cffi_wchar_t; /* random pick... */ +#endif + +static cffi_wchar_t _cffi_to_c_wchar_t(PyObject *init) +{ + if (sizeof(cffi_wchar_t) == 2) + return (cffi_wchar_t)_convert_to_char16_t(init); + else + return (cffi_wchar_t)_convert_to_char32_t(init); +} +static PyObject *_cffi_from_c_wchar_t(cffi_wchar_t x) { + if (sizeof(cffi_wchar_t) == 2) { + cffi_char16_t input = x; + return _my_PyUnicode_FromChar16(&input, 1); + } + else { + cffi_char32_t input = x; + return _my_PyUnicode_FromChar32(&input, 1); + } +} +static int _cffi_to_c_wchar3216_t(PyObject *init) +{ + if (sizeof(cffi_wchar_t) == 4) + return (int)_convert_to_char16_t(init); + else + return (int)_convert_to_char32_t(init); +} +static PyObject *_cffi_from_c_wchar3216_t(int x) { + if (sizeof(cffi_wchar_t) == 4) { + cffi_char16_t input = x; + return _my_PyUnicode_FromChar16(&input, 1); + } + else { + cffi_char32_t input = x; + return _my_PyUnicode_FromChar32(&input, 1); + } +} + +struct _cffi_externpy_s; /* forward declaration */ +static void cffi_call_python(struct _cffi_externpy_s *, char *args); + +static void *cffi_exports[] = { + NULL, + _cffi_to_c_i8, + _cffi_to_c_u8, + _cffi_to_c_i16, + _cffi_to_c_u16, + _cffi_to_c_i32, + _cffi_to_c_u32, + _cffi_to_c_i64, + _cffi_to_c_u64, + _convert_to_char, + _cffi_from_c_pointer, + _cffi_to_c_pointer, + _cffi_get_struct_layout, + restore_errno, + save_errno, + _cffi_from_c_char, + convert_to_object, + convert_from_object, + convert_struct_to_owning_object, + _cffi_to_c_wchar_t, + _cffi_from_c_wchar_t, + _cffi_to_c_long_double, + _cffi_to_c__Bool, + _prepare_pointer_call_argument, + convert_array_from_object, + cffi_call_python, + _cffi_to_c_wchar3216_t, + _cffi_from_c_wchar3216_t, +}; + +static struct { const char *name; int value; } all_dlopen_flags[] = { + { "RTLD_LAZY", RTLD_LAZY }, + { "RTLD_NOW", RTLD_NOW }, + { "RTLD_GLOBAL", RTLD_GLOBAL }, +#ifdef RTLD_LOCAL + { "RTLD_LOCAL", RTLD_LOCAL }, +#else + { "RTLD_LOCAL", 0 }, +#endif +#ifdef RTLD_NODELETE + { "RTLD_NODELETE", RTLD_NODELETE }, +#endif +#ifdef RTLD_NOLOAD + { "RTLD_NOLOAD", RTLD_NOLOAD }, +#endif +#ifdef RTLD_DEEPBIND + { "RTLD_DEEPBIND", RTLD_DEEPBIND }, +#endif + { NULL, 0 } +}; + + +/************************************************************/ + +#include "cffi1_module.c" + +/************************************************************/ + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef FFIBackendModuleDef = { + PyModuleDef_HEAD_INIT, + "_cffi_backend", + NULL, + -1, + FFIBackendMethods, + NULL, NULL, NULL, NULL +}; +#define INITERROR return NULL + +PyMODINIT_FUNC +PyInit__cffi_backend(void) +#else +#define INITERROR return + +PyMODINIT_FUNC +init_cffi_backend(void) +#endif +{ + PyObject *m, *v; + int i; + static char init_done = 0; + + v = PySys_GetObject("version"); + if (v == NULL || !PyText_Check(v) || + strncmp(PyText_AS_UTF8(v), PY_VERSION, 3) != 0) { + PyErr_Format(PyExc_ImportError, + "this module was compiled for Python %c%c%c", + PY_VERSION[0], PY_VERSION[1], PY_VERSION[2]); + INITERROR; + } + +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&FFIBackendModuleDef); +#else + m = Py_InitModule("_cffi_backend", FFIBackendMethods); +#endif + + if (m == NULL) + INITERROR; + + if (unique_cache == NULL) { + unique_cache = PyDict_New(); + if (unique_cache == NULL) + INITERROR; + } + + if (PyType_Ready(&dl_type) < 0) + INITERROR; + if (PyType_Ready(&CTypeDescr_Type) < 0) + INITERROR; + if (PyType_Ready(&CField_Type) < 0) + INITERROR; + if (PyType_Ready(&CData_Type) < 0) + INITERROR; + if (PyType_Ready(&CDataOwning_Type) < 0) + INITERROR; + if (PyType_Ready(&CDataOwningGC_Type) < 0) + INITERROR; + if (PyType_Ready(&CDataGCP_Type) < 0) + INITERROR; + if (PyType_Ready(&CDataIter_Type) < 0) + INITERROR; + if (PyType_Ready(&MiniBuffer_Type) < 0) + INITERROR; + + if (!init_done) { + v = PyText_FromString("_cffi_backend"); + if (v == NULL || PyDict_SetItemString(CData_Type.tp_dict, + "__module__", v) < 0) + INITERROR; + v = PyText_FromString(""); + if (v == NULL || PyDict_SetItemString(CData_Type.tp_dict, + "__name__", v) < 0) + INITERROR; + init_done = 1; + } + + /* this is for backward compatibility only */ + v = PyCapsule_New((void *)cffi_exports, "cffi", NULL); + if (v == NULL || PyModule_AddObject(m, "_C_API", v) < 0) + INITERROR; + + v = PyText_FromString(CFFI_VERSION); + if (v == NULL || PyModule_AddObject(m, "__version__", v) < 0) + INITERROR; + + if (PyModule_AddIntConstant(m, "FFI_DEFAULT_ABI", FFI_DEFAULT_ABI) < 0 || +#if defined(MS_WIN32) && !defined(_WIN64) + PyModule_AddIntConstant(m, "FFI_STDCALL", FFI_STDCALL) < 0 || +#endif + PyModule_AddIntConstant(m, "FFI_CDECL", FFI_DEFAULT_ABI) < 0 || + +#ifdef MS_WIN32 +# ifdef _WIN64 + PyModule_AddIntConstant(m, "_WIN", 64) < 0 || /* win64 */ +# else + PyModule_AddIntConstant(m, "_WIN", 32) < 0 || /* win32 */ +# endif +#endif + 0) + INITERROR; + + for (i = 0; all_dlopen_flags[i].name != NULL; i++) { + if (PyModule_AddIntConstant(m, + all_dlopen_flags[i].name, + all_dlopen_flags[i].value) < 0) + INITERROR; + } + + Py_INCREF(&MiniBuffer_Type); + if (PyModule_AddObject(m, "buffer", (PyObject *)&MiniBuffer_Type) < 0) + INITERROR; + + init_cffi_tls(); + if (PyErr_Occurred()) + INITERROR; + init_cffi_tls_zombie(); + if (PyErr_Occurred()) + INITERROR; + + if (init_ffi_lib(m) < 0) + INITERROR; + +#if PY_MAJOR_VERSION >= 3 + if (init_file_emulator() < 0) + INITERROR; + return m; +#endif +} diff --git a/c/_cffi_backend.so b/c/_cffi_backend.so new file mode 100644 index 0000000..7055ae6 Binary files /dev/null and b/c/_cffi_backend.so differ diff --git a/c/_dummy_file_cffi_backend.py b/c/_dummy_file_cffi_backend.py new file mode 100644 index 0000000..e69de29 diff --git a/c/_dummy_file_libffi.py b/c/_dummy_file_libffi.py new file mode 100644 index 0000000..e69de29 diff --git a/c/call_python.c b/c/call_python.c new file mode 100644 index 0000000..8fdcb90 --- /dev/null +++ b/c/call_python.c @@ -0,0 +1,278 @@ +#if PY_VERSION_HEX >= 0x03080000 +# define Py_BUILD_CORE +/* for access to the fields of PyInterpreterState */ +# include "internal/pycore_pystate.h" +# undef Py_BUILD_CORE +#endif + +static PyObject *_get_interpstate_dict(void) +{ + /* Hack around to return a dict that is subinterpreter-local. + Does not return a new reference. Returns NULL in case of + error, but without setting any exception. (If called late + during shutdown, we *can't* set an exception!) + */ + static PyObject *attr_name = NULL; + PyThreadState *tstate; + PyObject *d, *builtins; + int err; + + tstate = PyThreadState_GET(); + if (tstate == NULL) { + /* no thread state! */ + return NULL; + } + + builtins = tstate->interp->builtins; + if (builtins == NULL) { + /* subinterpreter was cleared already, or is being cleared right now, + to a point that is too much for us to continue */ + return NULL; + } + + /* from there on, we know the (sub-)interpreter is still valid */ + + if (attr_name == NULL) { + attr_name = PyText_InternFromString("__cffi_backend_extern_py"); + if (attr_name == NULL) + goto error; + } + + d = PyDict_GetItem(builtins, attr_name); + if (d == NULL) { + d = PyDict_New(); + if (d == NULL) + goto error; + err = PyDict_SetItem(builtins, attr_name, d); + Py_DECREF(d); /* if successful, there is one ref left in builtins */ + if (err < 0) + goto error; + } + return d; + + error: + PyErr_Clear(); /* typically a MemoryError */ + return NULL; +} + +static PyObject *_ffi_def_extern_decorator(PyObject *outer_args, PyObject *fn) +{ + const char *s; + PyObject *error, *onerror, *infotuple, *old1; + int index, err; + const struct _cffi_global_s *g; + struct _cffi_externpy_s *externpy; + CTypeDescrObject *ct; + FFIObject *ffi; + builder_c_t *types_builder; + PyObject *name = NULL; + PyObject *interpstate_dict; + PyObject *interpstate_key; + + if (!PyArg_ParseTuple(outer_args, "OzOO", &ffi, &s, &error, &onerror)) + return NULL; + + if (s == NULL) { + name = PyObject_GetAttrString(fn, "__name__"); + if (name == NULL) + return NULL; + s = PyText_AsUTF8(name); + if (s == NULL) { + Py_DECREF(name); + return NULL; + } + } + + types_builder = &ffi->types_builder; + index = search_in_globals(&types_builder->ctx, s, strlen(s)); + if (index < 0) + goto not_found; + g = &types_builder->ctx.globals[index]; + if (_CFFI_GETOP(g->type_op) != _CFFI_OP_EXTERN_PYTHON) + goto not_found; + Py_XDECREF(name); + + ct = realize_c_type(types_builder, types_builder->ctx.types, + _CFFI_GETARG(g->type_op)); + if (ct == NULL) + return NULL; + + infotuple = prepare_callback_info_tuple(ct, fn, error, onerror, 0); + Py_DECREF(ct); + if (infotuple == NULL) + return NULL; + + /* don't directly attach infotuple to externpy: in the presence of + subinterpreters, each time we switch to a different + subinterpreter and call the C function, it will notice the + change and look up infotuple from the interpstate_dict. + */ + interpstate_dict = _get_interpstate_dict(); + if (interpstate_dict == NULL) { + Py_DECREF(infotuple); + return PyErr_NoMemory(); + } + + externpy = (struct _cffi_externpy_s *)g->address; + interpstate_key = PyLong_FromVoidPtr((void *)externpy); + if (interpstate_key == NULL) { + Py_DECREF(infotuple); + return NULL; + } + + err = PyDict_SetItem(interpstate_dict, interpstate_key, infotuple); + Py_DECREF(interpstate_key); + Py_DECREF(infotuple); /* interpstate_dict owns the last ref */ + if (err < 0) + return NULL; + + /* force _update_cache_to_call_python() to be called the next time + the C function invokes cffi_call_python, to update the cache */ + old1 = externpy->reserved1; + externpy->reserved1 = Py_None; /* a non-NULL value */ + Py_INCREF(Py_None); + Py_XDECREF(old1); + + /* return the function object unmodified */ + Py_INCREF(fn); + return fn; + + not_found: + PyErr_Format(FFIError, "ffi.def_extern('%s'): no 'extern \"Python\"' " + "function with this name", s); + Py_XDECREF(name); + return NULL; +} + + +static int _update_cache_to_call_python(struct _cffi_externpy_s *externpy) +{ + PyObject *interpstate_dict, *interpstate_key, *infotuple, *old1, *new1; + PyObject *old2; + + interpstate_dict = _get_interpstate_dict(); + if (interpstate_dict == NULL) + return 4; /* oops, shutdown issue? */ + + interpstate_key = PyLong_FromVoidPtr((void *)externpy); + if (interpstate_key == NULL) + goto error; + + infotuple = PyDict_GetItem(interpstate_dict, interpstate_key); + Py_DECREF(interpstate_key); + if (infotuple == NULL) + return 3; /* no ffi.def_extern() from this subinterpreter */ + + new1 = PyThreadState_GET()->interp->modules; + Py_INCREF(new1); + Py_INCREF(infotuple); + old1 = (PyObject *)externpy->reserved1; + old2 = (PyObject *)externpy->reserved2; + externpy->reserved1 = new1; /* holds a reference */ + externpy->reserved2 = infotuple; /* holds a reference (issue #246) */ + Py_XDECREF(old1); + Py_XDECREF(old2); + + return 0; /* no error */ + + error: + PyErr_Clear(); + return 2; /* out of memory? */ +} + +#if (defined(WITH_THREAD) && !defined(_MSC_VER) && \ + !defined(__amd64__) && !defined(__x86_64__) && \ + !defined(__i386__) && !defined(__i386)) +# if defined(HAVE_SYNC_SYNCHRONIZE) +# define read_barrier() __sync_synchronize() +# elif defined(_AIX) +# define read_barrier() __lwsync() +# elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +# include +# define read_barrier() __compiler_barrier() +# elif defined(__hpux) +# define read_barrier() _Asm_mf() +# else +# define read_barrier() /* missing */ +# warning "no definition for read_barrier(), missing synchronization for\ + multi-thread initialization in embedded mode" +# endif +#else +# define read_barrier() (void)0 +#endif + +static void cffi_call_python(struct _cffi_externpy_s *externpy, char *args) +{ + /* Invoked by the helpers generated from extern "Python" in the cdef. + + 'externpy' is a static structure that describes which of the + extern "Python" functions is called. It has got fields 'name' and + 'type_index' describing the function, and more reserved fields + that are initially zero. These reserved fields are set up by + ffi.def_extern(), which invokes _ffi_def_extern_decorator() above. + + 'args' is a pointer to an array of 8-byte entries. Each entry + contains an argument. If an argument is less than 8 bytes, only + the part at the beginning of the entry is initialized. If an + argument is 'long double' or a struct/union, then it is passed + by reference. + + 'args' is also used as the place to write the result to + (directly, even if more than 8 bytes). In all cases, 'args' is + at least 8 bytes in size. + */ + int err = 0; + + /* This read barrier is needed for _embedding.h. It is paired + with the write_barrier() there. Without this barrier, we can + in theory see the following situation: the Python + initialization code already ran (in another thread), and the + '_cffi_call_python' function pointer directed execution here; + but any number of other data could still be seen as + uninitialized below. For example, 'externpy' would still + contain NULLs even though it was correctly set up, or + 'interpreter_lock' (the GIL inside CPython) would still be seen + as NULL, or 'autoInterpreterState' (used by + PyGILState_Ensure()) would be NULL or contain bogus fields. + */ + read_barrier(); + + save_errno(); + + /* We need the infotuple here. We could always go through + _update_cache_to_call_python(), but to avoid the extra dict + lookups, we cache in (reserved1, reserved2) the last seen pair + (interp->modules, infotuple). The first item in this tuple is + a random PyObject that identifies the subinterpreter. + */ + if (externpy->reserved1 == NULL) { + /* Not initialized! We didn't call @ffi.def_extern() on this + externpy object from any subinterpreter at all. */ + err = 1; + } + else { + PyGILState_STATE state = gil_ensure(); + if (externpy->reserved1 != PyThreadState_GET()->interp->modules) { + /* Update the (reserved1, reserved2) cache. This will fail + if we didn't call @ffi.def_extern() in this particular + subinterpreter. */ + err = _update_cache_to_call_python(externpy); + } + if (!err) { + general_invoke_callback(0, args, args, externpy->reserved2); + } + gil_release(state); + } + if (err) { + static const char *msg[] = { + "no code was attached to it yet with @ffi.def_extern()", + "got internal exception (out of memory?)", + "@ffi.def_extern() was not called in the current subinterpreter", + "got internal exception (shutdown issue?)", + }; + fprintf(stderr, "extern \"Python\": function %s() called, " + "but %s. Returning 0.\n", externpy->name, msg[err-1]); + memset(args, 0, externpy->size_of_result); + } + restore_errno(); +} diff --git a/c/cdlopen.c b/c/cdlopen.c new file mode 100644 index 0000000..ad33bbd --- /dev/null +++ b/c/cdlopen.c @@ -0,0 +1,361 @@ +/* ffi.dlopen() interface with dlopen()/dlsym()/dlclose() */ + +static void *cdlopen_fetch(PyObject *libname, void *libhandle, + const char *symbol) +{ + void *address; + + if (libhandle == NULL) { + PyErr_Format(FFIError, "library '%s' has been closed", + PyText_AS_UTF8(libname)); + return NULL; + } + + dlerror(); /* clear error condition */ + address = dlsym(libhandle, symbol); + if (address == NULL) { + const char *error = dlerror(); + PyErr_Format(FFIError, "symbol '%s' not found in library '%s': %s", + symbol, PyText_AS_UTF8(libname), error); + } + return address; +} + +static void cdlopen_close_ignore_errors(void *libhandle) +{ + if (libhandle != NULL) + dlclose(libhandle); +} + +static int cdlopen_close(PyObject *libname, void *libhandle) +{ + if (libhandle != NULL && dlclose(libhandle) != 0) { + const char *error = dlerror(); + PyErr_Format(FFIError, "closing library '%s': %s", + PyText_AS_UTF8(libname), error); + return -1; + } + return 0; +} + +static PyObject *ffi_dlopen(PyObject *self, PyObject *args) +{ + const char *modname; + PyObject *temp, *result = NULL; + void *handle; + + handle = b_do_dlopen(args, &modname, &temp); + if (handle != NULL) + { + result = (PyObject *)lib_internal_new((FFIObject *)self, + modname, handle); + } + Py_XDECREF(temp); + return result; +} + +static PyObject *ffi_dlclose(PyObject *self, PyObject *args) +{ + LibObject *lib; + void *libhandle; + if (!PyArg_ParseTuple(args, "O!", &Lib_Type, &lib)) + return NULL; + + libhandle = lib->l_libhandle; + if (libhandle != NULL) + { + lib->l_libhandle = NULL; + + /* Clear the dict to force further accesses to do cdlopen_fetch() + again, and fail because the library was closed. */ + PyDict_Clear(lib->l_dict); + + if (cdlopen_close(lib->l_libname, libhandle) < 0) + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + + +static Py_ssize_t cdl_4bytes(char *src) +{ + /* read 4 bytes in little-endian order; return it as a signed integer */ + signed char *ssrc = (signed char *)src; + unsigned char *usrc = (unsigned char *)src; + return (ssrc[0] << 24) | (usrc[1] << 16) | (usrc[2] << 8) | usrc[3]; +} + +static _cffi_opcode_t cdl_opcode(char *src) +{ + return (_cffi_opcode_t)cdl_4bytes(src); +} + +typedef struct { + unsigned long long value; + int neg; +} cdl_intconst_t; + +static int _cdl_realize_global_int(struct _cffi_getconst_s *gc) +{ + /* The 'address' field of 'struct _cffi_global_s' is set to point + to this function in case ffiobj_init() sees constant integers. + This fishes around after the 'ctx->globals' array, which is + initialized to contain another array, this time of + 'cdl_intconst_t' structures. We get the nth one and it tells + us what to return. + */ + cdl_intconst_t *ic; + ic = (cdl_intconst_t *)(gc->ctx->globals + gc->ctx->num_globals); + ic += gc->gindex; + gc->value = ic->value; + return ic->neg; +} + +static int ffiobj_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + FFIObject *ffi; + static char *keywords[] = {"module_name", "_version", "_types", + "_globals", "_struct_unions", "_enums", + "_typenames", "_includes", NULL}; + char *ffiname = "?", *types = NULL, *building = NULL; + Py_ssize_t version = -1; + Py_ssize_t types_len = 0; + PyObject *globals = NULL, *struct_unions = NULL, *enums = NULL; + PyObject *typenames = NULL, *includes = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "|sns#O!O!O!O!O!:FFI", keywords, + &ffiname, &version, &types, &types_len, + &PyTuple_Type, &globals, + &PyTuple_Type, &struct_unions, + &PyTuple_Type, &enums, + &PyTuple_Type, &typenames, + &PyTuple_Type, &includes)) + return -1; + + ffi = (FFIObject *)self; + if (ffi->ctx_is_nonempty) { + PyErr_SetString(PyExc_ValueError, + "cannot call FFI.__init__() more than once"); + return -1; + } + ffi->ctx_is_nonempty = 1; + + if (version == -1 && types_len == 0) + return 0; + if (version < CFFI_VERSION_MIN || version > CFFI_VERSION_MAX) { + PyErr_Format(PyExc_ImportError, + "cffi out-of-line Python module '%s' has unknown " + "version %p", ffiname, (void *)version); + return -1; + } + + if (types_len > 0) { + /* unpack a string of 4-byte entries into an array of _cffi_opcode_t */ + _cffi_opcode_t *ntypes; + Py_ssize_t i, n = types_len / 4; + + building = PyMem_Malloc(n * sizeof(_cffi_opcode_t)); + if (building == NULL) + goto error; + ntypes = (_cffi_opcode_t *)building; + + for (i = 0; i < n; i++) { + ntypes[i] = cdl_opcode(types); + types += 4; + } + ffi->types_builder.ctx.types = ntypes; + ffi->types_builder.ctx.num_types = n; + building = NULL; + } + + if (globals != NULL) { + /* unpack a tuple alternating strings and ints, each two together + describing one global_s entry with no specified address or size. + The int is only used with integer constants. */ + struct _cffi_global_s *nglobs; + cdl_intconst_t *nintconsts; + Py_ssize_t i, n = PyTuple_GET_SIZE(globals) / 2; + + i = n * (sizeof(struct _cffi_global_s) + sizeof(cdl_intconst_t)); + building = PyMem_Malloc(i); + if (building == NULL) + goto error; + memset(building, 0, i); + nglobs = (struct _cffi_global_s *)building; + nintconsts = (cdl_intconst_t *)(nglobs + n); + + for (i = 0; i < n; i++) { + char *g = PyBytes_AS_STRING(PyTuple_GET_ITEM(globals, i * 2)); + nglobs[i].type_op = cdl_opcode(g); g += 4; + nglobs[i].name = g; + if (_CFFI_GETOP(nglobs[i].type_op) == _CFFI_OP_CONSTANT_INT || + _CFFI_GETOP(nglobs[i].type_op) == _CFFI_OP_ENUM) { + PyObject *o = PyTuple_GET_ITEM(globals, i * 2 + 1); + nglobs[i].address = &_cdl_realize_global_int; +#if PY_MAJOR_VERSION < 3 + if (PyInt_Check(o)) { + nintconsts[i].neg = PyInt_AS_LONG(o) <= 0; + nintconsts[i].value = (long long)PyInt_AS_LONG(o); + } + else +#endif + { + nintconsts[i].neg = PyObject_RichCompareBool(o, Py_False, + Py_LE); + nintconsts[i].value = PyLong_AsUnsignedLongLongMask(o); + if (PyErr_Occurred()) + goto error; + } + } + } + ffi->types_builder.ctx.globals = nglobs; + ffi->types_builder.ctx.num_globals = n; + building = NULL; + } + + if (struct_unions != NULL) { + /* unpack a tuple of struct/unions, each described as a sub-tuple; + the item 0 of each sub-tuple describes the struct/union, and + the items 1..N-1 describe the fields, if any */ + struct _cffi_struct_union_s *nstructs; + struct _cffi_field_s *nfields; + Py_ssize_t i, n = PyTuple_GET_SIZE(struct_unions); + Py_ssize_t nf = 0; /* total number of fields */ + + for (i = 0; i < n; i++) { + nf += PyTuple_GET_SIZE(PyTuple_GET_ITEM(struct_unions, i)) - 1; + } + i = (n * sizeof(struct _cffi_struct_union_s) + + nf * sizeof(struct _cffi_field_s)); + building = PyMem_Malloc(i); + if (building == NULL) + goto error; + memset(building, 0, i); + nstructs = (struct _cffi_struct_union_s *)building; + nfields = (struct _cffi_field_s *)(nstructs + n); + nf = 0; + + for (i = 0; i < n; i++) { + /* 'desc' is the tuple of strings (desc_struct, desc_field_1, ..) */ + PyObject *desc = PyTuple_GET_ITEM(struct_unions, i); + Py_ssize_t j, nf1 = PyTuple_GET_SIZE(desc) - 1; + char *s = PyBytes_AS_STRING(PyTuple_GET_ITEM(desc, 0)); + /* 's' is the first string, describing the struct/union */ + nstructs[i].type_index = cdl_4bytes(s); s += 4; + nstructs[i].flags = cdl_4bytes(s); s += 4; + nstructs[i].name = s; + if (nstructs[i].flags & (_CFFI_F_OPAQUE | _CFFI_F_EXTERNAL)) { + nstructs[i].size = (size_t)-1; + nstructs[i].alignment = -1; + nstructs[i].first_field_index = -1; + nstructs[i].num_fields = 0; + assert(nf1 == 0); + } + else { + nstructs[i].size = (size_t)-2; + nstructs[i].alignment = -2; + nstructs[i].first_field_index = nf; + nstructs[i].num_fields = nf1; + } + for (j = 0; j < nf1; j++) { + char *f = PyBytes_AS_STRING(PyTuple_GET_ITEM(desc, j + 1)); + /* 'f' is one of the other strings beyond the first one, + describing one field each */ + nfields[nf].field_type_op = cdl_opcode(f); f += 4; + nfields[nf].field_offset = (size_t)-1; + if (_CFFI_GETOP(nfields[nf].field_type_op) != _CFFI_OP_NOOP) { + nfields[nf].field_size = cdl_4bytes(f); f += 4; + } + else { + nfields[nf].field_size = (size_t)-1; + } + nfields[nf].name = f; + nf++; + } + } + ffi->types_builder.ctx.struct_unions = nstructs; + ffi->types_builder.ctx.fields = nfields; + ffi->types_builder.ctx.num_struct_unions = n; + building = NULL; + } + + if (enums != NULL) { + /* unpack a tuple of strings, each of which describes one enum_s + entry */ + struct _cffi_enum_s *nenums; + Py_ssize_t i, n = PyTuple_GET_SIZE(enums); + + i = n * sizeof(struct _cffi_enum_s); + building = PyMem_Malloc(i); + if (building == NULL) + goto error; + memset(building, 0, i); + nenums = (struct _cffi_enum_s *)building; + + for (i = 0; i < n; i++) { + char *e = PyBytes_AS_STRING(PyTuple_GET_ITEM(enums, i)); + /* 'e' is a string describing the enum */ + nenums[i].type_index = cdl_4bytes(e); e += 4; + nenums[i].type_prim = cdl_4bytes(e); e += 4; + nenums[i].name = e; e += strlen(e) + 1; + nenums[i].enumerators = e; + } + ffi->types_builder.ctx.enums = nenums; + ffi->types_builder.ctx.num_enums = n; + building = NULL; + } + + if (typenames != NULL) { + /* unpack a tuple of strings, each of which describes one typename_s + entry */ + struct _cffi_typename_s *ntypenames; + Py_ssize_t i, n = PyTuple_GET_SIZE(typenames); + + i = n * sizeof(struct _cffi_typename_s); + building = PyMem_Malloc(i); + if (building == NULL) + goto error; + memset(building, 0, i); + ntypenames = (struct _cffi_typename_s *)building; + + for (i = 0; i < n; i++) { + char *t = PyBytes_AS_STRING(PyTuple_GET_ITEM(typenames, i)); + /* 't' is a string describing the typename */ + ntypenames[i].type_index = cdl_4bytes(t); t += 4; + ntypenames[i].name = t; + } + ffi->types_builder.ctx.typenames = ntypenames; + ffi->types_builder.ctx.num_typenames = n; + building = NULL; + } + + if (includes != NULL) { + PyObject *included_libs; + + included_libs = PyTuple_New(PyTuple_GET_SIZE(includes)); + if (included_libs == NULL) + return -1; + + Py_INCREF(includes); + ffi->types_builder.included_ffis = includes; + ffi->types_builder.included_libs = included_libs; + } + + /* Above, we took directly some "char *" strings out of the strings, + typically from somewhere inside tuples. Keep them alive by + incref'ing the whole input arguments. */ + Py_INCREF(args); + Py_XINCREF(kwds); + ffi->types_builder._keepalive1 = args; + ffi->types_builder._keepalive2 = kwds; + return 0; + + error: + if (building != NULL) + PyMem_Free(building); + if (!PyErr_Occurred()) + PyErr_NoMemory(); + return -1; +} diff --git a/c/cffi1_module.c b/c/cffi1_module.c new file mode 100644 index 0000000..2b98e8e --- /dev/null +++ b/c/cffi1_module.c @@ -0,0 +1,231 @@ + +#include "parse_c_type.c" +#include "realize_c_type.c" + +#define CFFI_VERSION_MIN 0x2601 +#define CFFI_VERSION_CHAR16CHAR32 0x2801 +#define CFFI_VERSION_MAX 0x28FF + +typedef struct FFIObject_s FFIObject; +typedef struct LibObject_s LibObject; + +static PyTypeObject FFI_Type; /* forward */ +static PyTypeObject Lib_Type; /* forward */ + +#include "ffi_obj.c" +#include "cglob.c" +#include "lib_obj.c" +#include "cdlopen.c" +#include "commontypes.c" +#include "call_python.c" + + +static int init_ffi_lib(PyObject *m) +{ + PyObject *x; + int i, res; + static char init_done = 0; + + if (PyType_Ready(&FFI_Type) < 0) + return -1; + if (PyType_Ready(&Lib_Type) < 0) + return -1; + + if (!init_done) { + if (init_global_types_dict(FFI_Type.tp_dict) < 0) + return -1; + + FFIError = PyErr_NewException("ffi.error", NULL, NULL); + if (FFIError == NULL) + return -1; + if (PyDict_SetItemString(FFI_Type.tp_dict, "error", FFIError) < 0) + return -1; + if (PyDict_SetItemString(FFI_Type.tp_dict, "CType", + (PyObject *)&CTypeDescr_Type) < 0) + return -1; + if (PyDict_SetItemString(FFI_Type.tp_dict, "CData", + (PyObject *)&CData_Type) < 0) + return -1; + if (PyDict_SetItemString(FFI_Type.tp_dict, "buffer", + (PyObject *)&MiniBuffer_Type) < 0) + return -1; + + for (i = 0; all_dlopen_flags[i].name != NULL; i++) { + x = PyInt_FromLong(all_dlopen_flags[i].value); + if (x == NULL) + return -1; + res = PyDict_SetItemString(FFI_Type.tp_dict, + all_dlopen_flags[i].name, x); + Py_DECREF(x); + if (res < 0) + return -1; + } + init_done = 1; + } + + x = (PyObject *)&FFI_Type; + Py_INCREF(x); + if (PyModule_AddObject(m, "FFI", x) < 0) + return -1; + x = (PyObject *)&Lib_Type; + Py_INCREF(x); + if (PyModule_AddObject(m, "Lib", x) < 0) + return -1; + + return 0; +} + +static int make_included_tuples(char *module_name, + const char *const *ctx_includes, + PyObject **included_ffis, + PyObject **included_libs) +{ + Py_ssize_t num = 0; + const char *const *p_include; + + if (ctx_includes == NULL) + return 0; + + for (p_include = ctx_includes; *p_include; p_include++) { + num++; + } + *included_ffis = PyTuple_New(num); + *included_libs = PyTuple_New(num); + if (*included_ffis == NULL || *included_libs == NULL) + goto error; + + num = 0; + for (p_include = ctx_includes; *p_include; p_include++) { + PyObject *included_ffi, *included_lib; + PyObject *m = PyImport_ImportModule(*p_include); + if (m == NULL) + goto import_error; + + included_ffi = PyObject_GetAttrString(m, "ffi"); + PyTuple_SET_ITEM(*included_ffis, num, included_ffi); + + included_lib = (included_ffi == NULL) ? NULL : + PyObject_GetAttrString(m, "lib"); + PyTuple_SET_ITEM(*included_libs, num, included_lib); + + Py_DECREF(m); + if (included_lib == NULL) + goto import_error; + + if (!FFIObject_Check(included_ffi) || + !LibObject_Check(included_lib)) + goto import_error; + num++; + } + return 0; + + import_error: + PyErr_Format(PyExc_ImportError, + "while loading %.200s: failed to import ffi, lib from %.200s", + module_name, *p_include); + error: + Py_XDECREF(*included_ffis); *included_ffis = NULL; + Py_XDECREF(*included_libs); *included_libs = NULL; + return -1; +} + +static PyObject *_my_Py_InitModule(char *module_name) +{ +#if PY_MAJOR_VERSION >= 3 + struct PyModuleDef *module_def, local_module_def = { + PyModuleDef_HEAD_INIT, + module_name, + NULL, + -1, + NULL, NULL, NULL, NULL, NULL + }; + /* note: the 'module_def' is allocated dynamically and leaks, + but anyway the C extension module can never be unloaded */ + module_def = PyMem_Malloc(sizeof(struct PyModuleDef)); + if (module_def == NULL) + return PyErr_NoMemory(); + *module_def = local_module_def; + return PyModule_Create(module_def); +#else + return Py_InitModule(module_name, NULL); +#endif +} + +static PyObject *b_init_cffi_1_0_external_module(PyObject *self, PyObject *arg) +{ + PyObject *m, *modules_dict; + FFIObject *ffi; + LibObject *lib; + Py_ssize_t version, num_exports; + char *module_name, *exports, *module_name_with_lib; + void **raw; + const struct _cffi_type_context_s *ctx; + + raw = (void **)PyLong_AsVoidPtr(arg); + if (raw == NULL) + return NULL; + + module_name = (char *)raw[0]; + version = (Py_ssize_t)raw[1]; + exports = (char *)raw[2]; + ctx = (const struct _cffi_type_context_s *)raw[3]; + + if (version < CFFI_VERSION_MIN || version > CFFI_VERSION_MAX) { + if (!PyErr_Occurred()) + PyErr_Format(PyExc_ImportError, + "cffi extension module '%s' uses an unknown version tag %p. " + "This module might need a more recent version of cffi " + "than the one currently installed, which is %s", + module_name, (void *)version, CFFI_VERSION); + return NULL; + } + + /* initialize the exports array */ + num_exports = 25; + if (ctx->flags & 1) /* set to mean that 'extern "Python"' is used */ + num_exports = 26; + if (version >= CFFI_VERSION_CHAR16CHAR32) + num_exports = 28; + memcpy(exports, (char *)cffi_exports, num_exports * sizeof(void *)); + + /* make the module object */ + m = _my_Py_InitModule(module_name); + if (m == NULL) + return NULL; + + /* build the FFI and Lib object inside this new module */ + ffi = ffi_internal_new(&FFI_Type, ctx); + Py_XINCREF(ffi); /* make the ffi object really immortal */ + if (ffi == NULL || PyModule_AddObject(m, "ffi", (PyObject *)ffi) < 0) + return NULL; + + lib = lib_internal_new(ffi, module_name, NULL); + if (lib == NULL || PyModule_AddObject(m, "lib", (PyObject *)lib) < 0) + return NULL; + + if (make_included_tuples(module_name, ctx->includes, + &ffi->types_builder.included_ffis, + &lib->l_types_builder->included_libs) < 0) + return NULL; + + /* add manually 'module_name.lib' in sys.modules: + see test_import_from_lib */ + modules_dict = PySys_GetObject("modules"); + if (!modules_dict) + return NULL; + module_name_with_lib = alloca(strlen(module_name) + 5); + strcpy(module_name_with_lib, module_name); + strcat(module_name_with_lib, ".lib"); + if (PyDict_SetItemString(modules_dict, module_name_with_lib, + (PyObject *)lib) < 0) + return NULL; + +#if PY_MAJOR_VERSION >= 3 + /* add manually 'module_name' in sys.modules: it seems that + Py_InitModule() is not enough to do that */ + if (PyDict_SetItemString(modules_dict, module_name, m) < 0) + return NULL; +#endif + + return m; +} diff --git a/c/cglob.c b/c/cglob.c new file mode 100644 index 0000000..9ee4025 --- /dev/null +++ b/c/cglob.c @@ -0,0 +1,113 @@ + +typedef void *(*gs_fetch_addr_fn)(void); + +typedef struct { + PyObject_HEAD + + PyObject *gs_name; + CTypeDescrObject *gs_type; + char *gs_data; + gs_fetch_addr_fn gs_fetch_addr; + +} GlobSupportObject; + +static void glob_support_dealloc(GlobSupportObject *gs) +{ + Py_DECREF(gs->gs_name); + Py_DECREF(gs->gs_type); + PyObject_Del(gs); +} + +static PyTypeObject GlobSupport_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "FFIGlobSupport", + sizeof(GlobSupportObject), + 0, + (destructor)glob_support_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ +}; + +#define GlobSupport_Check(ob) (Py_TYPE(ob) == &GlobSupport_Type) + +static PyObject *make_global_var(PyObject *name, CTypeDescrObject *type, + char *addr, gs_fetch_addr_fn fetch_addr) +{ + GlobSupportObject *gs = PyObject_New(GlobSupportObject, &GlobSupport_Type); + if (gs == NULL) + return NULL; + + Py_INCREF(name); + Py_INCREF(type); + gs->gs_name = name; + gs->gs_type = type; + gs->gs_data = addr; + gs->gs_fetch_addr = fetch_addr; + return (PyObject *)gs; +} + +static void *fetch_global_var_addr(GlobSupportObject *gs) +{ + void *data; + if (gs->gs_data != NULL) { + data = gs->gs_data; + } + else { + Py_BEGIN_ALLOW_THREADS + restore_errno(); + data = gs->gs_fetch_addr(); + save_errno(); + Py_END_ALLOW_THREADS + } + if (data == NULL) { + PyErr_Format(FFIError, "global variable '%s' is at address NULL", + PyText_AS_UTF8(gs->gs_name)); + return NULL; + } + return data; +} + +static PyObject *read_global_var(GlobSupportObject *gs) +{ + void *data = fetch_global_var_addr(gs); + if (data == NULL) + return NULL; + return convert_to_object(data, gs->gs_type); +} + +static int write_global_var(GlobSupportObject *gs, PyObject *obj) +{ + void *data = fetch_global_var_addr(gs); + if (data == NULL) + return -1; + return convert_from_object(data, gs->gs_type, obj); +} + +static PyObject *cg_addressof_global_var(GlobSupportObject *gs) +{ + void *data; + PyObject *x, *ptrtype = new_pointer_type(gs->gs_type); + if (ptrtype == NULL) + return NULL; + + data = fetch_global_var_addr(gs); + if (data != NULL) + x = new_simple_cdata(data, (CTypeDescrObject *)ptrtype); + else + x = NULL; + Py_DECREF(ptrtype); + return x; +} diff --git a/c/commontypes.c b/c/commontypes.c new file mode 100644 index 0000000..a41c2fd --- /dev/null +++ b/c/commontypes.c @@ -0,0 +1,216 @@ +/* This file must be kept in alphabetical order. See test_commontypes.py */ + +#define EQ(key, value) key "\0" value /* string concatenation */ +#ifdef _WIN64 +# define W32_64(X,Y) Y +# else +# define W32_64(X,Y) X +# endif + + +static const char *common_simple_types[] = { + +#ifdef MS_WIN32 /* Windows types */ + EQ("ATOM", "WORD"), + EQ("BOOL", "int"), + EQ("BOOLEAN", "BYTE"), + EQ("BYTE", "unsigned char"), + EQ("CCHAR", "char"), + EQ("CHAR", "char"), + EQ("COLORREF", "DWORD"), + EQ("DWORD", "unsigned long"), + EQ("DWORD32", "unsigned int"), + EQ("DWORD64", "unsigned long long"), + EQ("DWORDLONG", "ULONGLONG"), + EQ("DWORD_PTR", "ULONG_PTR"), +#endif + + EQ("FILE", "struct _IO_FILE"), + +#ifdef MS_WIN32 /* more Windows types */ + EQ("FLOAT", "float"), + EQ("HACCEL", "HANDLE"), + EQ("HALF_PTR", W32_64("short","int")), + EQ("HANDLE", "PVOID"), + EQ("HBITMAP", "HANDLE"), + EQ("HBRUSH", "HANDLE"), + EQ("HCOLORSPACE", "HANDLE"), + EQ("HCONV", "HANDLE"), + EQ("HCONVLIST", "HANDLE"), + EQ("HCURSOR", "HICON"), + EQ("HDC", "HANDLE"), + EQ("HDDEDATA", "HANDLE"), + EQ("HDESK", "HANDLE"), + EQ("HDROP", "HANDLE"), + EQ("HDWP", "HANDLE"), + EQ("HENHMETAFILE", "HANDLE"), + EQ("HFILE", "int"), + EQ("HFONT", "HANDLE"), + EQ("HGDIOBJ", "HANDLE"), + EQ("HGLOBAL", "HANDLE"), + EQ("HHOOK", "HANDLE"), + EQ("HICON", "HANDLE"), + EQ("HINSTANCE", "HANDLE"), + EQ("HKEY", "HANDLE"), + EQ("HKL", "HANDLE"), + EQ("HLOCAL", "HANDLE"), + EQ("HMENU", "HANDLE"), + EQ("HMETAFILE", "HANDLE"), + EQ("HMODULE", "HINSTANCE"), + EQ("HMONITOR", "HANDLE"), + EQ("HPALETTE", "HANDLE"), + EQ("HPEN", "HANDLE"), + EQ("HRESULT", "LONG"), + EQ("HRGN", "HANDLE"), + EQ("HRSRC", "HANDLE"), + EQ("HSZ", "HANDLE"), + EQ("HWND", "HANDLE"), + EQ("INT", "int"), + EQ("INT16", "short"), + EQ("INT32", "int"), + EQ("INT64", "long long"), + EQ("INT8", "signed char"), + EQ("INT_PTR", W32_64("int","long long")), + EQ("LANGID", "WORD"), + EQ("LCID", "DWORD"), + EQ("LCTYPE", "DWORD"), + EQ("LGRPID", "DWORD"), + EQ("LONG", "long"), + EQ("LONG32", "int"), + EQ("LONG64", "long long"), + EQ("LONGLONG", "long long"), + EQ("LONG_PTR", W32_64("long","long long")), + EQ("LPARAM", "LONG_PTR"), + EQ("LPBOOL", "BOOL *"), + EQ("LPBYTE", "BYTE *"), + EQ("LPCOLORREF", "DWORD *"), + EQ("LPCSTR", "const char *"), + EQ("LPCVOID", "const void *"), + EQ("LPCWSTR", "const WCHAR *"), + EQ("LPDWORD", "DWORD *"), + EQ("LPHANDLE", "HANDLE *"), + EQ("LPINT", "int *"), + EQ("LPLONG", "long *"), + EQ("LPSTR", "CHAR *"), + EQ("LPVOID", "void *"), + EQ("LPWORD", "WORD *"), + EQ("LPWSTR", "WCHAR *"), + EQ("LRESULT", "LONG_PTR"), + EQ("PBOOL", "BOOL *"), + EQ("PBOOLEAN", "BOOLEAN *"), + EQ("PBYTE", "BYTE *"), + EQ("PCHAR", "CHAR *"), + EQ("PCSTR", "const CHAR *"), + EQ("PCWSTR", "const WCHAR *"), + EQ("PDWORD", "DWORD *"), + EQ("PDWORD32", "DWORD32 *"), + EQ("PDWORD64", "DWORD64 *"), + EQ("PDWORDLONG", "DWORDLONG *"), + EQ("PDWORD_PTR", "DWORD_PTR *"), + EQ("PFLOAT", "FLOAT *"), + EQ("PHALF_PTR", "HALF_PTR *"), + EQ("PHANDLE", "HANDLE *"), + EQ("PHKEY", "HKEY *"), + EQ("PINT", "int *"), + EQ("PINT16", "INT16 *"), + EQ("PINT32", "INT32 *"), + EQ("PINT64", "INT64 *"), + EQ("PINT8", "INT8 *"), + EQ("PINT_PTR", "INT_PTR *"), + EQ("PLCID", "PDWORD"), + EQ("PLONG", "LONG *"), + EQ("PLONG32", "LONG32 *"), + EQ("PLONG64", "LONG64 *"), + EQ("PLONGLONG", "LONGLONG *"), + EQ("PLONG_PTR", "LONG_PTR *"), + EQ("PSHORT", "SHORT *"), + EQ("PSIZE_T", "SIZE_T *"), + EQ("PSSIZE_T", "SSIZE_T *"), + EQ("PSTR", "CHAR *"), + EQ("PUCHAR", "UCHAR *"), + EQ("PUHALF_PTR", "UHALF_PTR *"), + EQ("PUINT", "UINT *"), + EQ("PUINT16", "UINT16 *"), + EQ("PUINT32", "UINT32 *"), + EQ("PUINT64", "UINT64 *"), + EQ("PUINT8", "UINT8 *"), + EQ("PUINT_PTR", "UINT_PTR *"), + EQ("PULONG", "ULONG *"), + EQ("PULONG32", "ULONG32 *"), + EQ("PULONG64", "ULONG64 *"), + EQ("PULONGLONG", "ULONGLONG *"), + EQ("PULONG_PTR", "ULONG_PTR *"), + EQ("PUSHORT", "USHORT *"), + EQ("PVOID", "void *"), + EQ("PWCHAR", "WCHAR *"), + EQ("PWORD", "WORD *"), + EQ("PWSTR", "WCHAR *"), + EQ("QWORD", "unsigned long long"), + EQ("SC_HANDLE", "HANDLE"), + EQ("SC_LOCK", "LPVOID"), + EQ("SERVICE_STATUS_HANDLE", "HANDLE"), + EQ("SHORT", "short"), + EQ("SIZE_T", "ULONG_PTR"), + EQ("SSIZE_T", "LONG_PTR"), + EQ("UCHAR", "unsigned char"), + EQ("UHALF_PTR", W32_64("unsigned short","unsigned int")), + EQ("UINT", "unsigned int"), + EQ("UINT16", "unsigned short"), + EQ("UINT32", "unsigned int"), + EQ("UINT64", "unsigned long long"), + EQ("UINT8", "unsigned char"), + EQ("UINT_PTR", W32_64("unsigned int","unsigned long long")), + EQ("ULONG", "unsigned long"), + EQ("ULONG32", "unsigned int"), + EQ("ULONG64", "unsigned long long"), + EQ("ULONGLONG", "unsigned long long"), + EQ("ULONG_PTR", W32_64("unsigned long","unsigned long long")), + EQ("USHORT", "unsigned short"), + EQ("USN", "LONGLONG"), + EQ("VOID", "void"), + EQ("WCHAR", "wchar_t"), + EQ("WINSTA", "HANDLE"), + EQ("WORD", "unsigned short"), + EQ("WPARAM", "UINT_PTR"), +#endif + + EQ("bool", "_Bool"), +}; + + +#undef EQ +#undef W32_64 + +#define num_common_simple_types \ + (sizeof(common_simple_types) / sizeof(common_simple_types[0])) + + +static const char *get_common_type(const char *search, size_t search_len) +{ + const char *entry; + int index = search_sorted(common_simple_types, sizeof(const char *), + num_common_simple_types, search, search_len); + if (index < 0) + return NULL; + + entry = common_simple_types[index]; + return entry + strlen(entry) + 1; +} + +static PyObject *b__get_common_types(PyObject *self, PyObject *arg) +{ + int err; + size_t i; + for (i = 0; i < num_common_simple_types; i++) { + const char *s = common_simple_types[i]; + PyObject *o = PyText_FromString(s + strlen(s) + 1); + if (o == NULL) + return NULL; + err = PyDict_SetItemString(arg, s, o); + Py_DECREF(o); + if (err < 0) + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} diff --git a/c/ffi_obj.c b/c/ffi_obj.c new file mode 100644 index 0000000..1e8cc6f --- /dev/null +++ b/c/ffi_obj.c @@ -0,0 +1,1221 @@ + +/* An FFI object has methods like ffi.new(). It is also a container + for the type declarations (typedefs and structs) that you can use, + say in ffi.new(). + + CTypeDescrObjects are internally stored in the dict 'types_dict'. + The types_dict is lazily filled with CTypeDescrObjects made from + reading a _cffi_type_context_s structure. + + In "modern" mode, the FFI instance is made by the C extension + module originally created by recompile(). The _cffi_type_context_s + structure comes from global data in the C extension module. + + In "compatibility" mode, an FFI instance is created explicitly by + the user, and its _cffi_type_context_s is initially empty. You + need to call ffi.cdef() to add more information to it. +*/ + +#define FFI_COMPLEXITY_OUTPUT 1200 /* xxx should grow as needed */ + +#define FFIObject_Check(op) PyObject_TypeCheck(op, &FFI_Type) +#define LibObject_Check(ob) ((Py_TYPE(ob) == &Lib_Type)) + +struct FFIObject_s { + PyObject_HEAD + PyObject *gc_wrefs, *gc_wrefs_freelist; + PyObject *init_once_cache; + struct _cffi_parse_info_s info; + char ctx_is_static, ctx_is_nonempty; + builder_c_t types_builder; +}; + +static FFIObject *ffi_internal_new(PyTypeObject *ffitype, + const struct _cffi_type_context_s *static_ctx) +{ + static _cffi_opcode_t internal_output[FFI_COMPLEXITY_OUTPUT]; + + FFIObject *ffi; + if (static_ctx != NULL) { + ffi = (FFIObject *)PyObject_GC_New(FFIObject, ffitype); + /* we don't call PyObject_GC_Track() here: from _cffi_init_module() + it is not needed, because in this case the ffi object is immortal */ + } + else { + ffi = (FFIObject *)ffitype->tp_alloc(ffitype, 0); + } + if (ffi == NULL) + return NULL; + + if (init_builder_c(&ffi->types_builder, static_ctx) < 0) { + Py_DECREF(ffi); + return NULL; + } + ffi->gc_wrefs = NULL; + ffi->gc_wrefs_freelist = NULL; + ffi->init_once_cache = NULL; + ffi->info.ctx = &ffi->types_builder.ctx; + ffi->info.output = internal_output; + ffi->info.output_size = FFI_COMPLEXITY_OUTPUT; + ffi->ctx_is_static = (static_ctx != NULL); + ffi->ctx_is_nonempty = (static_ctx != NULL); + return ffi; +} + +static void ffi_dealloc(FFIObject *ffi) +{ + PyObject_GC_UnTrack(ffi); + Py_XDECREF(ffi->gc_wrefs); + Py_XDECREF(ffi->gc_wrefs_freelist); + Py_XDECREF(ffi->init_once_cache); + + free_builder_c(&ffi->types_builder, ffi->ctx_is_static); + + Py_TYPE(ffi)->tp_free((PyObject *)ffi); +} + +static int ffi_traverse(FFIObject *ffi, visitproc visit, void *arg) +{ + Py_VISIT(ffi->types_builder.types_dict); + Py_VISIT(ffi->types_builder.included_ffis); + Py_VISIT(ffi->types_builder.included_libs); + Py_VISIT(ffi->gc_wrefs); + return 0; +} + +static PyObject *ffiobj_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + /* user-facing initialization code, for explicit FFI() calls */ + return (PyObject *)ffi_internal_new(type, NULL); +} + +/* forward, declared in cdlopen.c because it's mostly useful for this case */ +static int ffiobj_init(PyObject *self, PyObject *args, PyObject *kwds); + +static PyObject *ffi_fetch_int_constant(FFIObject *ffi, const char *name, + int recursion) +{ + int index; + + index = search_in_globals(&ffi->types_builder.ctx, name, strlen(name)); + if (index >= 0) { + const struct _cffi_global_s *g; + g = &ffi->types_builder.ctx.globals[index]; + + switch (_CFFI_GETOP(g->type_op)) { + case _CFFI_OP_CONSTANT_INT: + case _CFFI_OP_ENUM: + return realize_global_int(&ffi->types_builder, index); + + default: + PyErr_Format(FFIError, + "function, global variable or non-integer constant " + "'%.200s' must be fetched from its original 'lib' " + "object", name); + return NULL; + } + } + + if (ffi->types_builder.included_ffis != NULL) { + Py_ssize_t i; + PyObject *included_ffis = ffi->types_builder.included_ffis; + + if (recursion > 100) { + PyErr_SetString(PyExc_RuntimeError, + "recursion overflow in ffi.include() delegations"); + return NULL; + } + + for (i = 0; i < PyTuple_GET_SIZE(included_ffis); i++) { + FFIObject *ffi1; + PyObject *x; + + ffi1 = (FFIObject *)PyTuple_GET_ITEM(included_ffis, i); + x = ffi_fetch_int_constant(ffi1, name, recursion + 1); + if (x != NULL || PyErr_Occurred()) + return x; + } + } + return NULL; /* no exception set, means "not found" */ +} + +#define ACCEPT_STRING 1 +#define ACCEPT_CTYPE 2 +#define ACCEPT_CDATA 4 +#define ACCEPT_ALL (ACCEPT_STRING | ACCEPT_CTYPE | ACCEPT_CDATA) +#define CONSIDER_FN_AS_FNPTR 8 + +static CTypeDescrObject *_ffi_bad_type(FFIObject *ffi, const char *input_text) +{ + size_t length = strlen(input_text); + char *extra; + + if (length > 500) { + extra = ""; + } + else { + char *p; + size_t i, num_spaces = ffi->info.error_location; + extra = alloca(length + num_spaces + 4); + p = extra; + *p++ = '\n'; + for (i = 0; i < length; i++) { + if (' ' <= input_text[i] && input_text[i] < 0x7f) + *p++ = input_text[i]; + else if (input_text[i] == '\t' || input_text[i] == '\n') + *p++ = ' '; + else + *p++ = '?'; + } + *p++ = '\n'; + memset(p, ' ', num_spaces); + p += num_spaces; + *p++ = '^'; + *p++ = 0; + } + PyErr_Format(FFIError, "%s%s", ffi->info.error_message, extra); + return NULL; +} + +static CTypeDescrObject *_ffi_type(FFIObject *ffi, PyObject *arg, + int accept) +{ + /* Returns the CTypeDescrObject from the user-supplied 'arg'. + Does not return a new reference! + */ + if ((accept & ACCEPT_STRING) && PyText_Check(arg)) { + PyObject *types_dict = ffi->types_builder.types_dict; + PyObject *x = PyDict_GetItem(types_dict, arg); + + if (x == NULL) { + const char *input_text = PyText_AS_UTF8(arg); + int err, index = parse_c_type(&ffi->info, input_text); + if (index < 0) + return _ffi_bad_type(ffi, input_text); + + x = realize_c_type_or_func(&ffi->types_builder, + ffi->info.output, index); + if (x == NULL) + return NULL; + + /* Cache under the name given by 'arg', in addition to the + fact that the same ct is probably already cached under + its standardized name. In a few cases, it is not, e.g. + if it is a primitive; for the purpose of this function, + the important point is the following line, which makes + sure that in any case the next _ffi_type() with the same + 'arg' will succeed early, in PyDict_GetItem() above. + */ + err = PyDict_SetItem(types_dict, arg, x); + Py_DECREF(x); /* we know it was written in types_dict (unless out + of mem), so there is at least that ref left */ + if (err < 0) + return NULL; + } + + if (CTypeDescr_Check(x)) + return (CTypeDescrObject *)x; + else if (accept & CONSIDER_FN_AS_FNPTR) + return unwrap_fn_as_fnptr(x); + else + return unexpected_fn_type(x); + } + else if ((accept & ACCEPT_CTYPE) && CTypeDescr_Check(arg)) { + return (CTypeDescrObject *)arg; + } + else if ((accept & ACCEPT_CDATA) && CData_Check(arg)) { + return ((CDataObject *)arg)->c_type; + } +#if PY_MAJOR_VERSION < 3 + else if (PyUnicode_Check(arg)) { + CTypeDescrObject *result; + arg = PyUnicode_AsASCIIString(arg); + if (arg == NULL) + return NULL; + result = _ffi_type(ffi, arg, accept); + Py_DECREF(arg); + return result; + } +#endif + else { + const char *m1 = (accept & ACCEPT_STRING) ? "string" : ""; + const char *m2 = (accept & ACCEPT_CTYPE) ? "ctype object" : ""; + const char *m3 = (accept & ACCEPT_CDATA) ? "cdata object" : ""; + const char *s12 = (*m1 && (*m2 || *m3)) ? " or " : ""; + const char *s23 = (*m2 && *m3) ? " or " : ""; + PyErr_Format(PyExc_TypeError, "expected a %s%s%s%s%s, got '%.200s'", + m1, s12, m2, s23, m3, + Py_TYPE(arg)->tp_name); + return NULL; + } +} + +PyDoc_STRVAR(ffi_sizeof_doc, +"Return the size in bytes of the argument.\n" +"It can be a string naming a C type, or a 'cdata' instance."); + +static PyObject *ffi_sizeof(FFIObject *self, PyObject *arg) +{ + Py_ssize_t size; + + if (CData_Check(arg)) { + size = direct_sizeof_cdata((CDataObject *)arg); + } + else { + CTypeDescrObject *ct = _ffi_type(self, arg, ACCEPT_ALL); + if (ct == NULL) + return NULL; + size = ct->ct_size; + if (size < 0) { + PyErr_Format(FFIError, "don't know the size of ctype '%s'", + ct->ct_name); + return NULL; + } + } + return PyInt_FromSsize_t(size); +} + +PyDoc_STRVAR(ffi_alignof_doc, +"Return the natural alignment size in bytes of the argument.\n" +"It can be a string naming a C type, or a 'cdata' instance."); + +static PyObject *ffi_alignof(FFIObject *self, PyObject *arg) +{ + int align; + CTypeDescrObject *ct = _ffi_type(self, arg, ACCEPT_ALL); + if (ct == NULL) + return NULL; + + align = get_alignment(ct); + if (align < 0) + return NULL; + return PyInt_FromLong(align); +} + +PyDoc_STRVAR(ffi_typeof_doc, +"Parse the C type given as a string and return the\n" +"corresponding object.\n" +"It can also be used on 'cdata' instance to get its C type."); + +static PyObject *_cpyextfunc_type_index(PyObject *x); /* forward */ + +static PyObject *ffi_typeof(FFIObject *self, PyObject *arg) +{ + PyObject *x = (PyObject *)_ffi_type(self, arg, ACCEPT_STRING|ACCEPT_CDATA); + if (x != NULL) { + Py_INCREF(x); + } + else { + x = _cpyextfunc_type_index(arg); + } + return x; +} + +PyDoc_STRVAR(ffi_new_doc, +"Allocate an instance according to the specified C type and return a\n" +"pointer to it. The specified C type must be either a pointer or an\n" +"array: ``new('X *')`` allocates an X and returns a pointer to it,\n" +"whereas ``new('X[n]')`` allocates an array of n X'es and returns an\n" +"array referencing it (which works mostly like a pointer, like in C).\n" +"You can also use ``new('X[]', n)`` to allocate an array of a\n" +"non-constant length n.\n" +"\n" +"The memory is initialized following the rules of declaring a global\n" +"variable in C: by default it is zero-initialized, but an explicit\n" +"initializer can be given which can be used to fill all or part of the\n" +"memory.\n" +"\n" +"When the returned object goes out of scope, the memory is\n" +"freed. In other words the returned object has ownership of\n" +"the value of type 'cdecl' that it points to. This means that the raw\n" +"data can be used as long as this object is kept alive, but must not be\n" +"used for a longer time. Be careful about that when copying the\n" +"pointer to the memory somewhere else, e.g. into another structure."); + +static PyObject *_ffi_new(FFIObject *self, PyObject *args, PyObject *kwds, + const cffi_allocator_t *allocator) +{ + CTypeDescrObject *ct; + PyObject *arg, *init = Py_None; + static char *keywords[] = {"cdecl", "init", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:new", keywords, + &arg, &init)) + return NULL; + + ct = _ffi_type(self, arg, ACCEPT_STRING|ACCEPT_CTYPE); + if (ct == NULL) + return NULL; + + return direct_newp(ct, init, allocator); +} + +static PyObject *ffi_new(FFIObject *self, PyObject *args, PyObject *kwds) +{ + return _ffi_new(self, args, kwds, &default_allocator); +} + +static PyObject *_ffi_new_with_allocator(PyObject *allocator, PyObject *args, + PyObject *kwds) +{ + cffi_allocator_t alloc1; + PyObject *my_alloc, *my_free; + my_alloc = PyTuple_GET_ITEM(allocator, 1); + my_free = PyTuple_GET_ITEM(allocator, 2); + alloc1.ca_alloc = (my_alloc == Py_None ? NULL : my_alloc); + alloc1.ca_free = (my_free == Py_None ? NULL : my_free); + alloc1.ca_dont_clear = (PyTuple_GET_ITEM(allocator, 3) == Py_False); + + return _ffi_new((FFIObject *)PyTuple_GET_ITEM(allocator, 0), + args, kwds, &alloc1); +} + +PyDoc_STRVAR(ffi_new_allocator_doc, +"Return a new allocator, i.e. a function that behaves like ffi.new()\n" +"but uses the provided low-level 'alloc' and 'free' functions.\n" +"\n" +"'alloc' is called with the size as argument. If it returns NULL, a\n" +"MemoryError is raised. 'free' is called with the result of 'alloc'\n" +"as argument. Both can be either Python functions or directly C\n" +"functions. If 'free' is None, then no free function is called.\n" +"If both 'alloc' and 'free' are None, the default is used.\n" +"\n" +"If 'should_clear_after_alloc' is set to False, then the memory\n" +"returned by 'alloc' is assumed to be already cleared (or you are\n" +"fine with garbage); otherwise CFFI will clear it."); + +static PyObject *ffi_new_allocator(FFIObject *self, PyObject *args, + PyObject *kwds) +{ + PyObject *allocator, *result; + PyObject *my_alloc = Py_None, *my_free = Py_None; + int should_clear_after_alloc = 1; + static char *keywords[] = {"alloc", "free", "should_clear_after_alloc", + NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi:new_allocator", keywords, + &my_alloc, &my_free, + &should_clear_after_alloc)) + return NULL; + + if (my_alloc == Py_None && my_free != Py_None) { + PyErr_SetString(PyExc_TypeError, "cannot pass 'free' without 'alloc'"); + return NULL; + } + + allocator = PyTuple_Pack(4, + (PyObject *)self, + my_alloc, + my_free, + PyBool_FromLong(should_clear_after_alloc)); + if (allocator == NULL) + return NULL; + + { + static PyMethodDef md = {"allocator", + (PyCFunction)_ffi_new_with_allocator, + METH_VARARGS | METH_KEYWORDS}; + result = PyCFunction_New(&md, allocator); + } + Py_DECREF(allocator); + return result; +} + +PyDoc_STRVAR(ffi_cast_doc, +"Similar to a C cast: returns an instance of the named C\n" +"type initialized with the given 'source'. The source is\n" +"casted between integers or pointers of any type."); + +static PyObject *ffi_cast(FFIObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *ob, *arg; + if (!PyArg_ParseTuple(args, "OO:cast", &arg, &ob)) + return NULL; + + ct = _ffi_type(self, arg, ACCEPT_STRING|ACCEPT_CTYPE); + if (ct == NULL) + return NULL; + + return do_cast(ct, ob); +} + +PyDoc_STRVAR(ffi_string_doc, +"Return a Python string (or unicode string) from the 'cdata'. If\n" +"'cdata' is a pointer or array of characters or bytes, returns the\n" +"null-terminated string. The returned string extends until the first\n" +"null character, or at most 'maxlen' characters. If 'cdata' is an\n" +"array then 'maxlen' defaults to its length.\n" +"\n" +"If 'cdata' is a pointer or array of wchar_t, returns a unicode string\n" +"following the same rules.\n" +"\n" +"If 'cdata' is a single character or byte or a wchar_t, returns it as a\n" +"string or unicode string.\n" +"\n" +"If 'cdata' is an enum, returns the value of the enumerator as a\n" +"string, or 'NUMBER' if the value is out of range."); + +#define ffi_string b_string /* ffi_string() => b_string() + from _cffi_backend.c */ + +PyDoc_STRVAR(ffi_unpack_doc, +"Unpack an array of C data of the given length,\n" +"returning a Python string/unicode/list.\n" +"\n" +"If 'cdata' is a pointer to 'char', returns a byte string.\n" +"It does not stop at the first null. This is equivalent to:\n" +"ffi.buffer(cdata, length)[:]\n" +"\n" +"If 'cdata' is a pointer to 'wchar_t', returns a unicode string.\n" +"'length' is measured in wchar_t's; it is not the size in bytes.\n" +"\n" +"If 'cdata' is a pointer to anything else, returns a list of\n" +"'length' items. This is a faster equivalent to:\n" +"[cdata[i] for i in range(length)]"); + +#define ffi_unpack b_unpack /* ffi_unpack() => b_unpack() + from _cffi_backend.c */ + + +PyDoc_STRVAR(ffi_offsetof_doc, +"Return the offset of the named field inside the given structure or\n" +"array, which must be given as a C type name. You can give several\n" +"field names in case of nested structures. You can also give numeric\n" +"values which correspond to array items, in case of an array type."); + +static PyObject *ffi_offsetof(FFIObject *self, PyObject *args) +{ + PyObject *arg; + CTypeDescrObject *ct; + Py_ssize_t i, offset; + + if (PyTuple_Size(args) < 2) { + PyErr_SetString(PyExc_TypeError, + "offsetof() expects at least 2 arguments"); + return NULL; + } + + arg = PyTuple_GET_ITEM(args, 0); + ct = _ffi_type(self, arg, ACCEPT_STRING|ACCEPT_CTYPE); + if (ct == NULL) + return NULL; + + offset = 0; + for (i = 1; i < PyTuple_GET_SIZE(args); i++) { + Py_ssize_t ofs1; + ct = direct_typeoffsetof(ct, PyTuple_GET_ITEM(args, i), i > 1, &ofs1); + if (ct == NULL) + return NULL; + offset += ofs1; + } + return PyInt_FromSsize_t(offset); +} + +PyDoc_STRVAR(ffi_addressof_doc, +"Limited equivalent to the '&' operator in C:\n" +"\n" +"1. ffi.addressof() returns a cdata that is a\n" +"pointer to this struct or union.\n" +"\n" +"2. ffi.addressof(, field-or-index...) returns the address of a\n" +"field or array item inside the given structure or array, recursively\n" +"in case of nested structures or arrays.\n" +"\n" +"3. ffi.addressof(, \"name\") returns the address of the named\n" +"function or global variable."); + +static PyObject *address_of_global_var(PyObject *args); /* forward */ + +static PyObject *ffi_addressof(FFIObject *self, PyObject *args) +{ + PyObject *arg, *z, *result; + CTypeDescrObject *ct; + Py_ssize_t i, offset = 0; + int accepted_flags; + + if (PyTuple_Size(args) < 1) { + PyErr_SetString(PyExc_TypeError, + "addressof() expects at least 1 argument"); + return NULL; + } + + arg = PyTuple_GET_ITEM(args, 0); + if (LibObject_Check(arg)) { + /* case 3 in the docstring */ + return address_of_global_var(args); + } + + ct = _ffi_type(self, arg, ACCEPT_CDATA); + if (ct == NULL) + return NULL; + + if (PyTuple_GET_SIZE(args) == 1) { + /* case 1 in the docstring */ + accepted_flags = CT_STRUCT | CT_UNION | CT_ARRAY; + if ((ct->ct_flags & accepted_flags) == 0) { + PyErr_SetString(PyExc_TypeError, + "expected a cdata struct/union/array object"); + return NULL; + } + } + else { + /* case 2 in the docstring */ + accepted_flags = CT_STRUCT | CT_UNION | CT_ARRAY | CT_POINTER; + if ((ct->ct_flags & accepted_flags) == 0) { + PyErr_SetString(PyExc_TypeError, + "expected a cdata struct/union/array/pointer object"); + return NULL; + } + for (i = 1; i < PyTuple_GET_SIZE(args); i++) { + Py_ssize_t ofs1; + ct = direct_typeoffsetof(ct, PyTuple_GET_ITEM(args, i), + i > 1, &ofs1); + if (ct == NULL) + return NULL; + offset += ofs1; + } + } + + z = new_pointer_type(ct); + if (z == NULL) + return NULL; + + result = new_simple_cdata(((CDataObject *)arg)->c_data + offset, + (CTypeDescrObject *)z); + Py_DECREF(z); + return result; +} + +static PyObject *_combine_type_name_l(CTypeDescrObject *ct, + size_t extra_text_len) +{ + size_t base_name_len; + PyObject *result; + char *p; + + base_name_len = strlen(ct->ct_name); + result = PyBytes_FromStringAndSize(NULL, base_name_len + extra_text_len); + if (result == NULL) + return NULL; + + p = PyBytes_AS_STRING(result); + memcpy(p, ct->ct_name, ct->ct_name_position); + p += ct->ct_name_position; + p += extra_text_len; + memcpy(p, ct->ct_name + ct->ct_name_position, + base_name_len - ct->ct_name_position); + return result; +} + +PyDoc_STRVAR(ffi_getctype_doc, +"Return a string giving the C type 'cdecl', which may be itself a\n" +"string or a object. If 'replace_with' is given, it gives\n" +"extra text to append (or insert for more complicated C types), like a\n" +"variable name, or '*' to get actually the C type 'pointer-to-cdecl'."); + +static PyObject *ffi_getctype(FFIObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *c_decl, *res; + char *p, *replace_with = ""; + int add_paren, add_space; + CTypeDescrObject *ct; + size_t replace_with_len; + static char *keywords[] = {"cdecl", "replace_with", NULL}; +#if PY_MAJOR_VERSION >= 3 + PyObject *u; +#endif + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|s:getctype", keywords, + &c_decl, &replace_with)) + return NULL; + + ct = _ffi_type(self, c_decl, ACCEPT_STRING|ACCEPT_CTYPE); + if (ct == NULL) + return NULL; + + while (replace_with[0] != 0 && isspace(replace_with[0])) + replace_with++; + replace_with_len = strlen(replace_with); + while (replace_with_len > 0 && isspace(replace_with[replace_with_len - 1])) + replace_with_len--; + + add_paren = (replace_with[0] == '*' && + ((ct->ct_flags & CT_ARRAY) != 0)); + add_space = (!add_paren && replace_with_len > 0 && + replace_with[0] != '[' && replace_with[0] != '('); + + res = _combine_type_name_l(ct, replace_with_len + add_space + 2*add_paren); + if (res == NULL) + return NULL; + + p = PyBytes_AS_STRING(res) + ct->ct_name_position; + if (add_paren) + *p++ = '('; + if (add_space) + *p++ = ' '; + memcpy(p, replace_with, replace_with_len); + if (add_paren) + p[replace_with_len] = ')'; + +#if PY_MAJOR_VERSION >= 3 + /* bytes -> unicode string */ + u = PyUnicode_DecodeLatin1(PyBytes_AS_STRING(res), + PyBytes_GET_SIZE(res), + NULL); + Py_DECREF(res); + res = u; +#endif + + return res; +} + +PyDoc_STRVAR(ffi_new_handle_doc, +"Return a non-NULL cdata of type 'void *' that contains an opaque\n" +"reference to the argument, which can be any Python object. To cast it\n" +"back to the original object, use from_handle(). You must keep alive\n" +"the cdata object returned by new_handle()!"); + +static PyObject *ffi_new_handle(FFIObject *self, PyObject *arg) +{ + /* g_ct_voidp is equal to */ + return newp_handle(g_ct_voidp, arg); +} + +PyDoc_STRVAR(ffi_from_handle_doc, +"Cast a 'void *' back to a Python object. Must be used *only* on the\n" +"pointers returned by new_handle(), and *only* as long as the exact\n" +"cdata object returned by new_handle() is still alive (somewhere else\n" +"in the program). Failure to follow these rules will crash."); + +#define ffi_from_handle b_from_handle /* ffi_from_handle => b_from_handle + from _cffi_backend.c */ + +PyDoc_STRVAR(ffi_from_buffer_doc, +"Return a that points to the data of the given Python\n" +"object, which must support the buffer interface. Note that this is\n" +"not meant to be used on the built-in types str or unicode\n" +"(you can build 'char[]' arrays explicitly) but only on objects\n" +"containing large quantities of raw data in some other format, like\n" +"'array.array' or numpy arrays."); + +static PyObject *ffi_from_buffer(FFIObject *self, PyObject *args, + PyObject *kwds) +{ + PyObject *cdecl1, *python_buf = NULL; + CTypeDescrObject *ct; + int require_writable = 0; + static char *keywords[] = {"cdecl", "python_buffer", + "require_writable", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Oi:from_buffer", keywords, + &cdecl1, &python_buf, &require_writable)) + return NULL; + + if (python_buf == NULL) { + python_buf = cdecl1; + ct = g_ct_chararray; + } + else { + ct = _ffi_type(self, cdecl1, ACCEPT_STRING|ACCEPT_CTYPE); + if (ct == NULL) + return NULL; + } + return direct_from_buffer(ct, python_buf, require_writable); +} + +PyDoc_STRVAR(ffi_gc_doc, +"Return a new cdata object that points to the same data.\n" +"Later, when this new cdata object is garbage-collected,\n" +"'destructor(old_cdata_object)' will be called.\n" +"\n" +"The optional 'size' gives an estimate of the size, used to\n" +"trigger the garbage collection more eagerly. So far only used\n" +"on PyPy. It tells the GC that the returned object keeps alive\n" +"roughly 'size' bytes of external memory."); + +#define ffi_gc b_gcp /* ffi_gc() => b_gcp() + from _cffi_backend.c */ + +PyDoc_STRVAR(ffi_def_extern_doc, +"A decorator. Attaches the decorated Python function to the C code\n" +"generated for the 'extern \"Python\"' function of the same name.\n" +"Calling the C function will then invoke the Python function.\n" +"\n" +"Optional arguments: 'name' is the name of the C function, if\n" +"different from the Python function; and 'error' and 'onerror'\n" +"handle what occurs if the Python function raises an exception\n" +"(see the docs for details)."); + +/* forward; see call_python.c */ +static PyObject *_ffi_def_extern_decorator(PyObject *, PyObject *); + +static PyObject *ffi_def_extern(FFIObject *self, PyObject *args, + PyObject *kwds) +{ + static PyMethodDef md = {"def_extern_decorator", + (PyCFunction)_ffi_def_extern_decorator, METH_O}; + PyObject *name = Py_None, *error = Py_None; + PyObject *res, *onerror = Py_None; + static char *keywords[] = {"name", "error", "onerror", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", keywords, + &name, &error, &onerror)) + return NULL; + + args = Py_BuildValue("(OOOO)", (PyObject *)self, name, error, onerror); + if (args == NULL) + return NULL; + + res = PyCFunction_New(&md, args); + Py_DECREF(args); + return res; +} + +PyDoc_STRVAR(ffi_callback_doc, +"Return a callback object or a decorator making such a callback object.\n" +"'cdecl' must name a C function pointer type. The callback invokes the\n" +"specified 'python_callable' (which may be provided either directly or\n" +"via a decorator). Important: the callback object must be manually\n" +"kept alive for as long as the callback may be invoked from the C code."); + +static PyObject *_ffi_callback_decorator(PyObject *outer_args, PyObject *fn) +{ + PyObject *res, *old; + + old = PyTuple_GET_ITEM(outer_args, 1); + PyTuple_SET_ITEM(outer_args, 1, fn); + res = b_callback(NULL, outer_args); + PyTuple_SET_ITEM(outer_args, 1, old); + return res; +} + +static PyObject *ffi_callback(FFIObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *c_decl, *python_callable = Py_None, *error = Py_None; + PyObject *res, *onerror = Py_None; + static char *keywords[] = {"cdecl", "python_callable", "error", + "onerror", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", keywords, + &c_decl, &python_callable, &error, + &onerror)) + return NULL; + + c_decl = (PyObject *)_ffi_type(self, c_decl, ACCEPT_STRING | ACCEPT_CTYPE | + CONSIDER_FN_AS_FNPTR); + if (c_decl == NULL) + return NULL; + + args = Py_BuildValue("(OOOO)", c_decl, python_callable, error, onerror); + if (args == NULL) + return NULL; + + if (python_callable != Py_None) { + res = b_callback(NULL, args); + } + else { + static PyMethodDef md = {"callback_decorator", + (PyCFunction)_ffi_callback_decorator, METH_O}; + res = PyCFunction_New(&md, args); + } + Py_DECREF(args); + return res; +} + +#ifdef MS_WIN32 +PyDoc_STRVAR(ffi_getwinerror_doc, +"Return either the GetLastError() or the error number given by the\n" +"optional 'code' argument, as a tuple '(code, message)'."); + +#define ffi_getwinerror b_getwinerror /* ffi_getwinerror() => b_getwinerror() + from misc_win32.h */ +#endif + +PyDoc_STRVAR(ffi_errno_doc, "the value of 'errno' from/to the C calls"); + +static PyObject *ffi_get_errno(PyObject *self, void *closure) +{ + /* xxx maybe think about how to make the saved errno local + to an ffi instance */ + return b_get_errno(NULL, NULL); +} + +static int ffi_set_errno(PyObject *self, PyObject *newval, void *closure) +{ + PyObject *x = b_set_errno(NULL, newval); + if (x == NULL) + return -1; + Py_DECREF(x); + return 0; +} + +PyDoc_STRVAR(ffi_dlopen_doc, +"Load and return a dynamic library identified by 'name'. The standard\n" +"C library can be loaded by passing None.\n" +"\n" +"Note that functions and types declared with 'ffi.cdef()' are not\n" +"linked to a particular library, just like C headers. In the library\n" +"we only look for the actual (untyped) symbols at the time of their\n" +"first access."); + +PyDoc_STRVAR(ffi_dlclose_doc, +"Close a library obtained with ffi.dlopen(). After this call, access to\n" +"functions or variables from the library will fail (possibly with a\n" +"segmentation fault)."); + +static PyObject *ffi_dlopen(PyObject *self, PyObject *args); /* forward */ +static PyObject *ffi_dlclose(PyObject *self, PyObject *args); /* forward */ + +PyDoc_STRVAR(ffi_int_const_doc, +"Get the value of an integer constant.\n" +"\n" +"'ffi.integer_const(\"xxx\")' is equivalent to 'lib.xxx' if xxx names an\n" +"integer constant. The point of this function is limited to use cases\n" +"where you have an 'ffi' object but not any associated 'lib' object."); + +static PyObject *ffi_int_const(FFIObject *self, PyObject *args, PyObject *kwds) +{ + char *name; + PyObject *x; + static char *keywords[] = {"name", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", keywords, &name)) + return NULL; + + x = ffi_fetch_int_constant(self, name, 0); + + if (x == NULL && !PyErr_Occurred()) { + PyErr_Format(PyExc_AttributeError, + "integer constant '%.200s' not found", name); + } + return x; +} + +PyDoc_STRVAR(ffi_list_types_doc, +"Returns the user type names known to this FFI instance.\n" +"This returns a tuple containing three lists of names:\n" +"(typedef_names, names_of_structs, names_of_unions)"); + +static PyObject *ffi_list_types(FFIObject *self, PyObject *noargs) +{ + Py_ssize_t i, n1 = self->types_builder.ctx.num_typenames; + Py_ssize_t n23 = self->types_builder.ctx.num_struct_unions; + PyObject *o, *lst[3] = {NULL, NULL, NULL}, *result = NULL; + + lst[0] = PyList_New(n1); + if (lst[0] == NULL) + goto error; + lst[1] = PyList_New(0); + if (lst[1] == NULL) + goto error; + lst[2] = PyList_New(0); + if (lst[2] == NULL) + goto error; + + for (i = 0; i < n1; i++) { + o = PyText_FromString(self->types_builder.ctx.typenames[i].name); + if (o == NULL) + goto error; + PyList_SET_ITEM(lst[0], i, o); + } + + for (i = 0; i < n23; i++) { + const struct _cffi_struct_union_s *s; + int err, index; + + s = &self->types_builder.ctx.struct_unions[i]; + if (s->name[0] == '$') + continue; + + o = PyText_FromString(s->name); + if (o == NULL) + goto error; + index = (s->flags & _CFFI_F_UNION) ? 2 : 1; + err = PyList_Append(lst[index], o); + Py_DECREF(o); + if (err < 0) + goto error; + } + result = PyTuple_Pack(3, lst[0], lst[1], lst[2]); + /* fall-through */ + error: + Py_XDECREF(lst[2]); + Py_XDECREF(lst[1]); + Py_XDECREF(lst[0]); + return result; +} + +PyDoc_STRVAR(ffi_memmove_doc, +"ffi.memmove(dest, src, n) copies n bytes of memory from src to dest.\n" +"\n" +"Like the C function memmove(), the memory areas may overlap;\n" +"apart from that it behaves like the C function memcpy().\n" +"\n" +"'src' can be any cdata ptr or array, or any Python buffer object.\n" +"'dest' can be any cdata ptr or array, or a writable Python buffer\n" +"object. The size to copy, 'n', is always measured in bytes.\n" +"\n" +"Unlike other methods, this one supports all Python buffer including\n" +"byte strings and bytearrays---but it still does not support\n" +"non-contiguous buffers."); + +#define ffi_memmove b_memmove /* ffi_memmove() => b_memmove() + from _cffi_backend.c */ + +PyDoc_STRVAR(ffi_init_once_doc, +"init_once(function, tag): run function() once. More precisely,\n" +"'function()' is called the first time we see a given 'tag'.\n" +"\n" +"The return value of function() is remembered and returned by the current\n" +"and all future init_once() with the same tag. If init_once() is called\n" +"from multiple threads in parallel, all calls block until the execution\n" +"of function() is done. If function() raises an exception, it is\n" +"propagated and nothing is cached."); + +#if PY_MAJOR_VERSION < 3 +/* PyCapsule_New is redefined to be PyCObject_FromVoidPtr in _cffi_backend, + which gives 2.6 compatibility; but the destructor signature is different */ +static void _free_init_once_lock(void *lock) +{ + PyThread_free_lock((PyThread_type_lock)lock); +} +#else +static void _free_init_once_lock(PyObject *capsule) +{ + PyThread_type_lock lock; + lock = PyCapsule_GetPointer(capsule, "cffi_init_once_lock"); + if (lock != NULL) + PyThread_free_lock(lock); +} +#endif + +static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds) +{ + static char *keywords[] = {"func", "tag", NULL}; + PyObject *cache, *func, *tag, *tup, *res, *x, *lockobj; + PyThread_type_lock lock; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", keywords, &func, &tag)) + return NULL; + + /* a lot of fun with reference counting and error checking + in this function */ + + /* atomically get or create a new dict (no GIL release) */ + cache = self->init_once_cache; + if (cache == NULL) { + cache = PyDict_New(); + if (cache == NULL) + return NULL; + self->init_once_cache = cache; + } + + /* get the tuple from cache[tag], or make a new one: (False, lock) */ + tup = PyDict_GetItem(cache, tag); + if (tup == NULL) { + lock = PyThread_allocate_lock(); + if (lock == NULL) + return NULL; + x = PyCapsule_New(lock, "cffi_init_once_lock", _free_init_once_lock); + if (x == NULL) { + PyThread_free_lock(lock); + return NULL; + } + tup = PyTuple_Pack(2, Py_False, x); + Py_DECREF(x); + if (tup == NULL) + return NULL; + x = tup; + + /* Possible corner case if 'tag' is an object overriding __eq__ + in pure Python: the GIL may be released when we are running it. + We really need to call dict.setdefault(). */ + tup = PyObject_CallMethod(cache, "setdefault", "OO", tag, x); + Py_DECREF(x); + if (tup == NULL) + return NULL; + + Py_DECREF(tup); /* there is still a ref inside the dict */ + } + + res = PyTuple_GET_ITEM(tup, 1); + Py_INCREF(res); + + if (PyTuple_GET_ITEM(tup, 0) == Py_True) { + /* tup == (True, result): return the result. */ + return res; + } + + /* tup == (False, lock) */ + lockobj = res; + lock = (PyThread_type_lock)PyCapsule_GetPointer(lockobj, + "cffi_init_once_lock"); + if (lock == NULL) { + Py_DECREF(lockobj); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + PyThread_acquire_lock(lock, WAIT_LOCK); + Py_END_ALLOW_THREADS + + x = PyDict_GetItem(cache, tag); + if (x != NULL && PyTuple_GET_ITEM(x, 0) == Py_True) { + /* the real result was put in the dict while we were waiting + for PyThread_acquire_lock() above */ + res = PyTuple_GET_ITEM(x, 1); + Py_INCREF(res); + } + else { + res = PyObject_CallFunction(func, ""); + if (res != NULL) { + tup = PyTuple_Pack(2, Py_True, res); + if (tup == NULL || PyDict_SetItem(cache, tag, tup) < 0) { + Py_XDECREF(tup); + Py_DECREF(res); + res = NULL; + } + } + } + + PyThread_release_lock(lock); + Py_DECREF(lockobj); + return res; +} + +PyDoc_STRVAR(ffi_release_doc, +"Release now the resources held by a 'cdata' object from ffi.new(),\n" +"ffi.gc() or ffi.from_buffer(). The cdata object must not be used\n" +"afterwards.\n" +"\n" +"'ffi.release(cdata)' is equivalent to 'cdata.__exit__()'.\n" +"\n" +"Note that on CPython this method has no effect (so far) on objects\n" +"returned by ffi.new(), because the memory is allocated inline with the\n" +"cdata object and cannot be freed independently. It might be fixed in\n" +"future releases of cffi."); + +#define ffi_release b_release /* ffi_release() => b_release() + from _cffi_backend.c */ + + +#define METH_VKW (METH_VARARGS | METH_KEYWORDS) +static PyMethodDef ffi_methods[] = { + {"addressof", (PyCFunction)ffi_addressof, METH_VARARGS, ffi_addressof_doc}, + {"alignof", (PyCFunction)ffi_alignof, METH_O, ffi_alignof_doc}, + {"def_extern", (PyCFunction)ffi_def_extern, METH_VKW, ffi_def_extern_doc}, + {"callback", (PyCFunction)ffi_callback, METH_VKW, ffi_callback_doc}, + {"cast", (PyCFunction)ffi_cast, METH_VARARGS, ffi_cast_doc}, + {"dlclose", (PyCFunction)ffi_dlclose, METH_VARARGS, ffi_dlclose_doc}, + {"dlopen", (PyCFunction)ffi_dlopen, METH_VARARGS, ffi_dlopen_doc}, + {"from_buffer",(PyCFunction)ffi_from_buffer,METH_VKW, ffi_from_buffer_doc}, + {"from_handle",(PyCFunction)ffi_from_handle,METH_O, ffi_from_handle_doc}, + {"gc", (PyCFunction)ffi_gc, METH_VKW, ffi_gc_doc}, + {"getctype", (PyCFunction)ffi_getctype, METH_VKW, ffi_getctype_doc}, +#ifdef MS_WIN32 + {"getwinerror",(PyCFunction)ffi_getwinerror,METH_VKW, ffi_getwinerror_doc}, +#endif + {"init_once", (PyCFunction)ffi_init_once, METH_VKW, ffi_init_once_doc}, + {"integer_const",(PyCFunction)ffi_int_const,METH_VKW, ffi_int_const_doc}, + {"list_types", (PyCFunction)ffi_list_types, METH_NOARGS, ffi_list_types_doc}, + {"memmove", (PyCFunction)ffi_memmove, METH_VKW, ffi_memmove_doc}, + {"new", (PyCFunction)ffi_new, METH_VKW, ffi_new_doc}, +{"new_allocator",(PyCFunction)ffi_new_allocator,METH_VKW,ffi_new_allocator_doc}, + {"new_handle", (PyCFunction)ffi_new_handle, METH_O, ffi_new_handle_doc}, + {"offsetof", (PyCFunction)ffi_offsetof, METH_VARARGS, ffi_offsetof_doc}, + {"release", (PyCFunction)ffi_release, METH_O, ffi_release_doc}, + {"sizeof", (PyCFunction)ffi_sizeof, METH_O, ffi_sizeof_doc}, + {"string", (PyCFunction)ffi_string, METH_VKW, ffi_string_doc}, + {"typeof", (PyCFunction)ffi_typeof, METH_O, ffi_typeof_doc}, + {"unpack", (PyCFunction)ffi_unpack, METH_VKW, ffi_unpack_doc}, + {NULL} +}; + +static PyGetSetDef ffi_getsets[] = { + {"errno", ffi_get_errno, ffi_set_errno, ffi_errno_doc}, + {NULL} +}; + +static PyTypeObject FFI_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "CompiledFFI", + sizeof(FFIObject), + 0, + (destructor)ffi_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)ffi_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + ffi_methods, /* tp_methods */ + 0, /* tp_members */ + ffi_getsets, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + ffiobj_init, /* tp_init */ + 0, /* tp_alloc */ + ffiobj_new, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + + +static PyObject * +_fetch_external_struct_or_union(const struct _cffi_struct_union_s *s, + PyObject *included_ffis, int recursion) +{ + Py_ssize_t i; + + if (included_ffis == NULL) + return NULL; + + if (recursion > 100) { + PyErr_SetString(PyExc_RuntimeError, + "recursion overflow in ffi.include() delegations"); + return NULL; + } + + for (i = 0; i < PyTuple_GET_SIZE(included_ffis); i++) { + FFIObject *ffi1; + const struct _cffi_struct_union_s *s1; + int sindex; + PyObject *x; + + ffi1 = (FFIObject *)PyTuple_GET_ITEM(included_ffis, i); + sindex = search_in_struct_unions(&ffi1->types_builder.ctx, s->name, + strlen(s->name)); + if (sindex < 0) /* not found at all */ + continue; + s1 = &ffi1->types_builder.ctx.struct_unions[sindex]; + if ((s1->flags & (_CFFI_F_EXTERNAL | _CFFI_F_UNION)) + == (s->flags & _CFFI_F_UNION)) { + /* s1 is not external, and the same kind (struct or union) as s */ + return _realize_c_struct_or_union(&ffi1->types_builder, sindex); + } + /* not found, look more recursively */ + x = _fetch_external_struct_or_union( + s, ffi1->types_builder.included_ffis, recursion + 1); + if (x != NULL || PyErr_Occurred()) + return x; /* either found, or got an error */ + } + return NULL; /* not found at all, leave without an error */ +} diff --git a/c/file_emulator.h b/c/file_emulator.h new file mode 100644 index 0000000..82a34c0 --- /dev/null +++ b/c/file_emulator.h @@ -0,0 +1,93 @@ + +/* Emulation of PyFile_Check() and PyFile_AsFile() for Python 3. */ + +static PyObject *PyIOBase_TypeObj; + +static int init_file_emulator(void) +{ + if (PyIOBase_TypeObj == NULL) { + PyObject *io = PyImport_ImportModule("_io"); + if (io == NULL) + return -1; + PyIOBase_TypeObj = PyObject_GetAttrString(io, "_IOBase"); + if (PyIOBase_TypeObj == NULL) + return -1; + } + return 0; +} + + +#define PyFile_Check(p) PyObject_IsInstance(p, PyIOBase_TypeObj) + + +static void _close_file_capsule(PyObject *ob_capsule) +{ + FILE *f = (FILE *)PyCapsule_GetPointer(ob_capsule, "FILE"); + if (f != NULL) + fclose(f); +} + + +static FILE *PyFile_AsFile(PyObject *ob_file) +{ + PyObject *ob, *ob_capsule = NULL, *ob_mode = NULL; + FILE *f; + int fd; + const char *mode; + + ob = PyObject_CallMethod(ob_file, "flush", NULL); + if (ob == NULL) + goto fail; + Py_DECREF(ob); + + ob_capsule = PyObject_GetAttrString(ob_file, "__cffi_FILE"); + if (ob_capsule == NULL) { + PyErr_Clear(); + + fd = PyObject_AsFileDescriptor(ob_file); + if (fd < 0) + goto fail; + + ob_mode = PyObject_GetAttrString(ob_file, "mode"); + if (ob_mode == NULL) + goto fail; + mode = PyText_AsUTF8(ob_mode); + if (mode == NULL) + goto fail; + + fd = dup(fd); + if (fd < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto fail; + } + + f = fdopen(fd, mode); + if (f == NULL) { + close(fd); + PyErr_SetFromErrno(PyExc_OSError); + goto fail; + } + setbuf(f, NULL); /* non-buffered */ + Py_DECREF(ob_mode); + ob_mode = NULL; + + ob_capsule = PyCapsule_New(f, "FILE", _close_file_capsule); + if (ob_capsule == NULL) { + fclose(f); + goto fail; + } + + if (PyObject_SetAttrString(ob_file, "__cffi_FILE", ob_capsule) < 0) + goto fail; + } + else { + f = PyCapsule_GetPointer(ob_capsule, "FILE"); + } + Py_DECREF(ob_capsule); /* assumes still at least one reference */ + return f; + + fail: + Py_XDECREF(ob_mode); + Py_XDECREF(ob_capsule); + return NULL; +} diff --git a/c/lib_obj.c b/c/lib_obj.c new file mode 100644 index 0000000..7cd40ec --- /dev/null +++ b/c/lib_obj.c @@ -0,0 +1,712 @@ + +/* A Lib object is what is in the "lib" attribute of a C extension + module originally created by recompile(). + + A Lib object is special in the sense that it has a custom + __getattr__ which returns C globals, functions and constants. The + original idea was to raise AttributeError for anything else, even + attrs like '__class__', but it breaks various things; now, standard + attrs are returned, but in the unlikely case where a user cdef()s + the same name, then the standard attr is hidden (and the various + things like introspection might break). + + A Lib object has got a reference to the _cffi_type_context_s + structure, which is used to create lazily the objects returned by + __getattr__. +*/ + +struct CPyExtFunc_s { + PyMethodDef md; + void *direct_fn; + int type_index; + char doc[1]; +}; + +struct LibObject_s { + PyObject_HEAD + builder_c_t *l_types_builder; /* same as the one on the ffi object */ + PyObject *l_dict; /* content, built lazily */ + PyObject *l_libname; /* some string that gives the name of the lib */ + FFIObject *l_ffi; /* reference back to the ffi object */ + void *l_libhandle; /* the dlopen()ed handle, if any */ +}; + +static struct CPyExtFunc_s *_cpyextfunc_get(PyObject *x) +{ + PyObject *y; + LibObject *lo; + PyCFunctionObject *fo; + + if (!PyCFunction_Check(x)) + return NULL; + y = PyCFunction_GET_SELF(x); + if (!LibObject_Check(y)) + return NULL; + + fo = (PyCFunctionObject *)x; + lo = (LibObject *)y; + if (lo->l_libname != fo->m_module) + return NULL; + + return (struct CPyExtFunc_s *)(fo->m_ml); +} + +static PyObject *_cpyextfunc_type(LibObject *lib, struct CPyExtFunc_s *exf) +{ + PyObject *tuple, *result; + tuple = realize_c_type_or_func(lib->l_types_builder, + lib->l_types_builder->ctx.types, + exf->type_index); + if (tuple == NULL) + return NULL; + + /* 'tuple' is a tuple of length 1 containing the real CT_FUNCTIONPTR + object */ + result = PyTuple_GetItem(tuple, 0); + Py_XINCREF(result); + Py_DECREF(tuple); + return result; +} + +static PyObject *_cpyextfunc_type_index(PyObject *x) +{ + struct CPyExtFunc_s *exf; + LibObject *lib; + + assert(PyErr_Occurred()); + exf = _cpyextfunc_get(x); + if (exf == NULL) + return NULL; /* still the same exception is set */ + + PyErr_Clear(); + + lib = (LibObject *)PyCFunction_GET_SELF(x); + return _cpyextfunc_type(lib, exf); +} + +static void cdlopen_close_ignore_errors(void *libhandle); /* forward */ +static void *cdlopen_fetch(PyObject *libname, void *libhandle, + const char *symbol); + +static void lib_dealloc(LibObject *lib) +{ + PyObject_GC_UnTrack(lib); + cdlopen_close_ignore_errors(lib->l_libhandle); + Py_DECREF(lib->l_dict); + Py_DECREF(lib->l_libname); + Py_DECREF(lib->l_ffi); + PyObject_GC_Del(lib); +} + +static int lib_traverse(LibObject *lib, visitproc visit, void *arg) +{ + Py_VISIT(lib->l_dict); + Py_VISIT(lib->l_libname); + Py_VISIT(lib->l_ffi); + return 0; +} + +static PyObject *lib_repr(LibObject *lib) +{ + return PyText_FromFormat("", + PyText_AS_UTF8(lib->l_libname)); +} + +static PyObject *lib_build_cpython_func(LibObject *lib, + const struct _cffi_global_s *g, + const char *s, int flags) +{ + /* First make sure the argument types and return type are really + built. The C extension code can then assume that they are, + by calling _cffi_type(). + */ + PyObject *result = NULL; + CTypeDescrObject **pfargs = NULL; + CTypeDescrObject *fresult; + Py_ssize_t nargs = 0; + struct CPyExtFunc_s *xfunc; + int i, type_index = _CFFI_GETARG(g->type_op); + _cffi_opcode_t *opcodes = lib->l_types_builder->ctx.types; + static const char *const format = ";\n\nCFFI C function from %s.lib"; + const char *libname = PyText_AS_UTF8(lib->l_libname); + struct funcbuilder_s funcbuilder; + + /* return type: */ + fresult = realize_c_func_return_type(lib->l_types_builder, opcodes, + type_index); + if (fresult == NULL) + goto error; + + /* argument types: */ + /* note that if the arguments are already built, they have a + pointer in the 'opcodes' array, and GETOP() returns a + random even value. But OP_FUNCTION_END is odd, so the + condition below still works correctly. */ + i = type_index + 1; + while (_CFFI_GETOP(opcodes[i]) != _CFFI_OP_FUNCTION_END) + i++; + pfargs = alloca(sizeof(CTypeDescrObject *) * (i - type_index - 1)); + i = type_index + 1; + while (_CFFI_GETOP(opcodes[i]) != _CFFI_OP_FUNCTION_END) { + CTypeDescrObject *ct = realize_c_type(lib->l_types_builder, opcodes, i); + if (ct == NULL) + goto error; + pfargs[nargs++] = ct; + i++; + } + + memset(&funcbuilder, 0, sizeof(funcbuilder)); + if (fb_build_name(&funcbuilder, g->name, pfargs, nargs, fresult, 0) < 0) + goto error; + + /* The few bytes of memory we allocate here appear to leak, but + this is not a real leak. Indeed, CPython never unloads its C + extension modules. There is only one PyMem_Malloc() per real + C function in a CFFI C extension module. That means that this + PyMem_Malloc() could also have been written with a static + global variable generated for each CPYTHON_BLTN defined in the + C extension, and the effect would be the same (but a bit more + complicated). + */ + xfunc = PyMem_Malloc(sizeof(struct CPyExtFunc_s) + + funcbuilder.nb_bytes + + strlen(format) + strlen(libname)); + if (xfunc == NULL) { + PyErr_NoMemory(); + goto error; + } + memset((char *)xfunc, 0, sizeof(struct CPyExtFunc_s)); + assert(g->address); + xfunc->md.ml_meth = (PyCFunction)g->address; + xfunc->md.ml_flags = flags; + xfunc->md.ml_name = g->name; + xfunc->md.ml_doc = xfunc->doc; + xfunc->direct_fn = g->size_or_direct_fn; + xfunc->type_index = type_index; + + /* build the docstring */ + funcbuilder.bufferp = xfunc->doc; + if (fb_build_name(&funcbuilder, g->name, pfargs, nargs, fresult, 0) < 0) + goto error; + sprintf(funcbuilder.bufferp - 1, format, libname); + /* done building the docstring */ + + result = PyCFunction_NewEx(&xfunc->md, (PyObject *)lib, lib->l_libname); + /* fall-through */ + error: + Py_XDECREF(fresult); + while (nargs > 0) { + --nargs; + Py_DECREF(pfargs[nargs]); + } + return result; +} + +static PyObject *lib_build_and_cache_attr(LibObject *lib, PyObject *name, + int recursion) +{ + /* does not return a new reference! */ + PyObject *x; + int index; + const struct _cffi_global_s *g; + CTypeDescrObject *ct; + builder_c_t *types_builder = lib->l_types_builder; + const char *s = PyText_AsUTF8(name); + if (s == NULL) + return NULL; + + index = search_in_globals(&types_builder->ctx, s, strlen(s)); + if (index < 0) { + + if (types_builder->included_libs != NULL) { + Py_ssize_t i; + PyObject *included_ffis = types_builder->included_ffis; + PyObject *included_libs = types_builder->included_libs; + + if (recursion > 100) { + PyErr_SetString(PyExc_RuntimeError, + "recursion overflow in ffi.include() delegations"); + return NULL; + } + + for (i = 0; i < PyTuple_GET_SIZE(included_libs); i++) { + LibObject *lib1; + + lib1 = (LibObject *)PyTuple_GET_ITEM(included_libs, i); + if (lib1 != NULL) { + x = PyDict_GetItem(lib1->l_dict, name); + if (x != NULL) { + Py_INCREF(x); + goto found; + } + x = lib_build_and_cache_attr(lib1, name, recursion + 1); + if (x != NULL) { + Py_INCREF(x); + goto found; + } + } + else { + FFIObject *ffi1; + + ffi1 = (FFIObject *)PyTuple_GetItem(included_ffis, i); + if (ffi1 == NULL) + return NULL; + x = ffi_fetch_int_constant(ffi1, s, recursion + 1); + if (x != NULL) + goto found; + } + if (PyErr_Occurred()) + return NULL; + } + } + + if (recursion > 0) + return NULL; /* no error set, continue looking elsewhere */ + + PyErr_Format(PyExc_AttributeError, + "cffi library '%.200s' has no function, constant " + "or global variable named '%.200s'", + PyText_AS_UTF8(lib->l_libname), s); + return NULL; + } + + g = &types_builder->ctx.globals[index]; + + switch (_CFFI_GETOP(g->type_op)) { + + case _CFFI_OP_CPYTHON_BLTN_V: + x = lib_build_cpython_func(lib, g, s, METH_VARARGS); + break; + + case _CFFI_OP_CPYTHON_BLTN_N: + x = lib_build_cpython_func(lib, g, s, METH_NOARGS); + break; + + case _CFFI_OP_CPYTHON_BLTN_O: + x = lib_build_cpython_func(lib, g, s, METH_O); + break; + + case _CFFI_OP_CONSTANT_INT: + case _CFFI_OP_ENUM: + { + /* a constant integer whose value, in an "unsigned long long", + is obtained by calling the function at g->address */ + x = realize_global_int(types_builder, index); + break; + } + + case _CFFI_OP_CONSTANT: + case _CFFI_OP_DLOPEN_CONST: + { + /* a constant which is not of integer type */ + char *data; + ct = realize_c_type(types_builder, types_builder->ctx.types, + _CFFI_GETARG(g->type_op)); + if (ct == NULL) + return NULL; + + if (ct->ct_size <= 0) { + PyErr_Format(FFIError, "constant '%s' is of type '%s', " + "whose size is not known", s, ct->ct_name); + return NULL; + } + if (g->address == NULL) { + /* for dlopen() style */ + assert(_CFFI_GETOP(g->type_op) == _CFFI_OP_DLOPEN_CONST); + data = cdlopen_fetch(lib->l_libname, lib->l_libhandle, s); + if (data == NULL) + return NULL; + } + else { + /* The few bytes of memory we allocate here appear to leak, but + this is not a real leak. Indeed, CPython never unloads its C + extension modules. There is only one PyMem_Malloc() per real + non-integer C constant in a CFFI C extension module. That + means that this PyMem_Malloc() could also have been written + with a static global variable generated for each OP_CONSTANT + defined in the C extension, and the effect would be the same + (but a bit more complicated). + + Note that we used to do alloca(), but see issue #198. We + could still do alloca(), or explicit PyMem_Free(), in some + cases; but there is no point and it only makes the remaining + less-common cases more suspicious. + */ + assert(_CFFI_GETOP(g->type_op) == _CFFI_OP_CONSTANT); + data = PyMem_Malloc(ct->ct_size); + if (data == NULL) { + PyErr_NoMemory(); + return NULL; + } + ((void(*)(char*))g->address)(data); + } + x = convert_to_object(data, ct); + Py_DECREF(ct); + break; + } + + case _CFFI_OP_GLOBAL_VAR: + { + /* global variable of the exact type specified here + (nowadays, only used by the ABI mode or backward + compatibility; see _CFFI_OP_GLOBAL_VAR_F for the API mode) + */ + Py_ssize_t g_size = (Py_ssize_t)g->size_or_direct_fn; + ct = realize_c_type(types_builder, types_builder->ctx.types, + _CFFI_GETARG(g->type_op)); + if (ct == NULL) + return NULL; + if (g_size != ct->ct_size && g_size != 0 && ct->ct_size > 0) { + PyErr_Format(FFIError, + "global variable '%.200s' should be %zd bytes " + "according to the cdef, but is actually %zd", + s, ct->ct_size, g_size); + x = NULL; + } + else { + void *address = g->address; + if (address == NULL) { + /* for dlopen() style */ + address = cdlopen_fetch(lib->l_libname, lib->l_libhandle, s); + if (address == NULL) + return NULL; + } + x = make_global_var(name, ct, address, NULL); + } + Py_DECREF(ct); + break; + } + + case _CFFI_OP_GLOBAL_VAR_F: + ct = realize_c_type(types_builder, types_builder->ctx.types, + _CFFI_GETARG(g->type_op)); + if (ct == NULL) + return NULL; + x = make_global_var(name, ct, NULL, (gs_fetch_addr_fn)g->address); + Py_DECREF(ct); + break; + + case _CFFI_OP_DLOPEN_FUNC: + { + /* For dlopen(): the function of the given 'name'. We use + dlsym() to get the address of something in the dynamic + library, which we interpret as being exactly a function of + the specified type. + */ + PyObject *ct1; + void *address = cdlopen_fetch(lib->l_libname, lib->l_libhandle, s); + if (address == NULL) + return NULL; + + ct1 = realize_c_type_or_func(types_builder, + types_builder->ctx.types, + _CFFI_GETARG(g->type_op)); + if (ct1 == NULL) + return NULL; + + assert(!CTypeDescr_Check(ct1)); /* must be a function */ + x = new_simple_cdata(address, unwrap_fn_as_fnptr(ct1)); + + Py_DECREF(ct1); + break; + } + + case _CFFI_OP_EXTERN_PYTHON: + /* for reading 'lib.bar' where bar is declared with extern "Python" */ + ct = realize_c_type(types_builder, types_builder->ctx.types, + _CFFI_GETARG(g->type_op)); + if (ct == NULL) + return NULL; + x = convert_to_object((char *)&g->size_or_direct_fn, ct); + Py_DECREF(ct); + break; + + default: + PyErr_Format(PyExc_NotImplementedError, "in lib_build_attr: op=%d", + (int)_CFFI_GETOP(g->type_op)); + return NULL; + } + + found: + if (x != NULL) { + int err = PyDict_SetItem(lib->l_dict, name, x); + Py_DECREF(x); + if (err < 0) /* else there is still one ref left in the dict */ + return NULL; + } + return x; +} + +#define LIB_GET_OR_CACHE_ADDR(x, lib, name, error) \ + do { \ + x = PyDict_GetItem(lib->l_dict, name); \ + if (x == NULL) { \ + x = lib_build_and_cache_attr(lib, name, 0); \ + if (x == NULL) { \ + error; \ + } \ + } \ + } while (0) + +static PyObject *_lib_dir1(LibObject *lib, int ignore_global_vars) +{ + const struct _cffi_global_s *g = lib->l_types_builder->ctx.globals; + int i, count = 0, total = lib->l_types_builder->ctx.num_globals; + PyObject *s, *lst = PyList_New(total); + if (lst == NULL) + return NULL; + + for (i = 0; i < total; i++) { + if (ignore_global_vars) { + int op = _CFFI_GETOP(g[i].type_op); + if (op == _CFFI_OP_GLOBAL_VAR || op == _CFFI_OP_GLOBAL_VAR_F) + continue; + } + s = PyText_FromString(g[i].name); + if (s == NULL) + goto error; + PyList_SET_ITEM(lst, count, s); + count++; + } + if (PyList_SetSlice(lst, count, total, NULL) < 0) + goto error; + return lst; + + error: + Py_DECREF(lst); + return NULL; +} + +static PyObject *_lib_dict(LibObject *lib) +{ + const struct _cffi_global_s *g = lib->l_types_builder->ctx.globals; + int i, total = lib->l_types_builder->ctx.num_globals; + PyObject *name, *x, *d = PyDict_New(); + if (d == NULL) + return NULL; + + for (i = 0; i < total; i++) { + name = PyText_FromString(g[i].name); + if (name == NULL) + goto error; + + LIB_GET_OR_CACHE_ADDR(x, lib, name, goto error); + + if (PyDict_SetItem(d, name, x) < 0) + goto error; + Py_DECREF(name); + } + return d; + + error: + Py_XDECREF(name); + Py_DECREF(d); + return NULL; +} + +static PyObject *lib_getattr(LibObject *lib, PyObject *name) +{ + const char *p; + PyObject *x; + LIB_GET_OR_CACHE_ADDR(x, lib, name, goto missing); + + if (GlobSupport_Check(x)) { + return read_global_var((GlobSupportObject *)x); + } + Py_INCREF(x); + return x; + + missing: + /*** ATTRIBUTEERROR IS SET HERE ***/ + p = PyText_AsUTF8(name); + if (p == NULL) + return NULL; + if (strcmp(p, "__all__") == 0) { + PyErr_Clear(); + return _lib_dir1(lib, 1); + } + if (strcmp(p, "__dict__") == 0) { + PyErr_Clear(); + return _lib_dict(lib); + } + if (strcmp(p, "__class__") == 0) { + PyErr_Clear(); + x = (PyObject *)&PyModule_Type; + /* ^^^ used to be Py_TYPE(lib). But HAAAAAACK! That makes + help() behave correctly. I couldn't find a more reasonable + way. Urgh. */ + Py_INCREF(x); + return x; + } + /* this hack is for Python 3.5, and also to give a more + module-like behavior */ + if (strcmp(p, "__name__") == 0) { + PyErr_Clear(); + return PyText_FromFormat("%s.lib", PyText_AS_UTF8(lib->l_libname)); + } +#if PY_MAJOR_VERSION >= 3 + if (strcmp(p, "__loader__") == 0 || strcmp(p, "__spec__") == 0) { + /* some more module-like behavior hacks */ + PyErr_Clear(); + Py_INCREF(Py_None); + return Py_None; + } +#endif + return NULL; +} + +static int lib_setattr(LibObject *lib, PyObject *name, PyObject *val) +{ + PyObject *x; + LIB_GET_OR_CACHE_ADDR(x, lib, name, return -1); + + if (val == NULL) { + PyErr_SetString(PyExc_AttributeError, "C attribute cannot be deleted"); + return -1; + } + + if (GlobSupport_Check(x)) { + return write_global_var((GlobSupportObject *)x, val); + } + + PyErr_Format(PyExc_AttributeError, + "cannot write to function or constant '%.200s'", + PyText_Check(name) ? PyText_AS_UTF8(name) : "?"); + return -1; +} + +static PyObject *lib_dir(PyObject *self, PyObject *noarg) +{ + return _lib_dir1((LibObject *)self, 0); +} + +static PyMethodDef lib_methods[] = { + {"__dir__", lib_dir, METH_NOARGS}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject Lib_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "CompiledLib", + sizeof(LibObject), + 0, + (destructor)lib_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)lib_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + (getattrofunc)lib_getattr, /* tp_getattro */ + (setattrofunc)lib_setattr, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)lib_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + lib_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(LibObject, l_dict), /* tp_dictoffset */ +}; + +static LibObject *lib_internal_new(FFIObject *ffi, const char *module_name, + void *dlopen_libhandle) +{ + LibObject *lib; + PyObject *libname, *dict; + + libname = PyText_FromString(module_name); + if (libname == NULL) + goto err1; + + dict = PyDict_New(); + if (dict == NULL) + goto err2; + + lib = (LibObject *)PyType_GenericAlloc(&Lib_Type, 0); + if (lib == NULL) + goto err3; + + lib->l_types_builder = &ffi->types_builder; + lib->l_dict = dict; + lib->l_libname = libname; + Py_INCREF(ffi); + lib->l_ffi = ffi; + lib->l_libhandle = dlopen_libhandle; + return lib; + + err3: + Py_DECREF(dict); + err2: + Py_DECREF(libname); + err1: + cdlopen_close_ignore_errors(dlopen_libhandle); + return NULL; +} + +static PyObject *address_of_global_var(PyObject *args) +{ + LibObject *lib; + PyObject *x, *o_varname; + char *varname; + + if (!PyArg_ParseTuple(args, "O!s", &Lib_Type, &lib, &varname)) + return NULL; + + /* rebuild a string from 'varname', to do typechecks and to force + a unicode back to a plain string (on python 2) */ + o_varname = PyText_FromString(varname); + if (o_varname == NULL) + return NULL; + + LIB_GET_OR_CACHE_ADDR(x, lib, o_varname, goto error); + Py_DECREF(o_varname); + if (GlobSupport_Check(x)) { + return cg_addressof_global_var((GlobSupportObject *)x); + } + else { + struct CPyExtFunc_s *exf = _cpyextfunc_get(x); + if (exf != NULL) { /* an OP_CPYTHON_BLTN: '&func' returns a cdata */ + PyObject *ct; + if (exf->direct_fn == NULL) { + Py_INCREF(x); /* backward compatibility */ + return x; + } + ct = _cpyextfunc_type(lib, exf); + if (ct == NULL) + return NULL; + x = new_simple_cdata(exf->direct_fn, (CTypeDescrObject *)ct); + Py_DECREF(ct); + return x; + } + if (CData_Check(x) && /* a constant functionptr cdata: 'f == &f' */ + (((CDataObject *)x)->c_type->ct_flags & CT_FUNCTIONPTR) != 0) { + Py_INCREF(x); + return x; + } + else { + PyErr_Format(PyExc_AttributeError, + "cannot take the address of the constant '%.200s'", + varname); + return NULL; + } + } + + error: + Py_DECREF(o_varname); + return NULL; +} diff --git a/c/libffi_msvc/LICENSE b/c/libffi_msvc/LICENSE new file mode 100644 index 0000000..f591795 --- /dev/null +++ b/c/libffi_msvc/LICENSE @@ -0,0 +1,20 @@ +libffi - Copyright (c) 1996-2003 Red Hat, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +``Software''), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/c/libffi_msvc/README b/c/libffi_msvc/README new file mode 100644 index 0000000..97a12cf --- /dev/null +++ b/c/libffi_msvc/README @@ -0,0 +1,502 @@ +This directory contains the libffi package, which is not part of GCC but +shipped with GCC as convenience. + +Copied without changes from CPython 2.7 head (e04e1f253ed8). + +Status +====== + +libffi-2.00 has not been released yet! This is a development snapshot! + +libffi-1.20 was released on October 5, 1998. Check the libffi web +page for updates: . + + +What is libffi? +=============== + +Compilers for high level languages generate code that follow certain +conventions. These conventions are necessary, in part, for separate +compilation to work. One such convention is the "calling +convention". The "calling convention" is essentially a set of +assumptions made by the compiler about where function arguments will +be found on entry to a function. A "calling convention" also specifies +where the return value for a function is found. + +Some programs may not know at the time of compilation what arguments +are to be passed to a function. For instance, an interpreter may be +told at run-time about the number and types of arguments used to call +a given function. Libffi can be used in such programs to provide a +bridge from the interpreter program to compiled code. + +The libffi library provides a portable, high level programming +interface to various calling conventions. This allows a programmer to +call any function specified by a call interface description at run +time. + +Ffi stands for Foreign Function Interface. A foreign function +interface is the popular name for the interface that allows code +written in one language to call code written in another language. The +libffi library really only provides the lowest, machine dependent +layer of a fully featured foreign function interface. A layer must +exist above libffi that handles type conversions for values passed +between the two languages. + + +Supported Platforms and Prerequisites +===================================== + +Libffi has been ported to: + + SunOS 4.1.3 & Solaris 2.x (SPARC-V8, SPARC-V9) + + Irix 5.3 & 6.2 (System V/o32 & n32) + + Intel x86 - Linux (System V ABI) + + Alpha - Linux and OSF/1 + + m68k - Linux (System V ABI) + + PowerPC - Linux (System V ABI, Darwin, AIX) + + ARM - Linux (System V ABI) + +Libffi has been tested with the egcs 1.0.2 gcc compiler. Chances are +that other versions will work. Libffi has also been built and tested +with the SGI compiler tools. + +On PowerPC, the tests failed (see the note below). + +You must use GNU make to build libffi. SGI's make will not work. +Sun's probably won't either. + +If you port libffi to another platform, please let me know! I assume +that some will be easy (x86 NetBSD), and others will be more difficult +(HP). + + +Installing libffi +================= + +[Note: before actually performing any of these installation steps, + you may wish to read the "Platform Specific Notes" below.] + +First you must configure the distribution for your particular +system. Go to the directory you wish to build libffi in and run the +"configure" program found in the root directory of the libffi source +distribution. + +You may want to tell configure where to install the libffi library and +header files. To do that, use the --prefix configure switch. Libffi +will install under /usr/local by default. + +If you want to enable extra run-time debugging checks use the the +--enable-debug configure switch. This is useful when your program dies +mysteriously while using libffi. + +Another useful configure switch is --enable-purify-safety. Using this +will add some extra code which will suppress certain warnings when you +are using Purify with libffi. Only use this switch when using +Purify, as it will slow down the library. + +Configure has many other options. Use "configure --help" to see them all. + +Once configure has finished, type "make". Note that you must be using +GNU make. SGI's make will not work. Sun's probably won't either. +You can ftp GNU make from prep.ai.mit.edu:/pub/gnu. + +To ensure that libffi is working as advertised, type "make test". + +To install the library and header files, type "make install". + + +Using libffi +============ + + The Basics + ---------- + +Libffi assumes that you have a pointer to the function you wish to +call and that you know the number and types of arguments to pass it, +as well as the return type of the function. + +The first thing you must do is create an ffi_cif object that matches +the signature of the function you wish to call. The cif in ffi_cif +stands for Call InterFace. To prepare a call interface object, use the +following function: + +ffi_status ffi_prep_cif(ffi_cif *cif, ffi_abi abi, + unsigned int nargs, + ffi_type *rtype, ffi_type **atypes); + + CIF is a pointer to the call interface object you wish + to initialize. + + ABI is an enum that specifies the calling convention + to use for the call. FFI_DEFAULT_ABI defaults + to the system's native calling convention. Other + ABI's may be used with care. They are system + specific. + + NARGS is the number of arguments this function accepts. + libffi does not yet support vararg functions. + + RTYPE is a pointer to an ffi_type structure that represents + the return type of the function. Ffi_type objects + describe the types of values. libffi provides + ffi_type objects for many of the native C types: + signed int, unsigned int, signed char, unsigned char, + etc. There is also a pointer ffi_type object and + a void ffi_type. Use &ffi_type_void for functions that + don't return values. + + ATYPES is a vector of ffi_type pointers. ARGS must be NARGS long. + If NARGS is 0, this is ignored. + + +ffi_prep_cif will return a status code that you are responsible +for checking. It will be one of the following: + + FFI_OK - All is good. + + FFI_BAD_TYPEDEF - One of the ffi_type objects that ffi_prep_cif + came across is bad. + + +Before making the call, the VALUES vector should be initialized +with pointers to the appropriate argument values. + +To call the the function using the initialized ffi_cif, use the +ffi_call function: + +void ffi_call(ffi_cif *cif, void *fn, void *rvalue, void **avalues); + + CIF is a pointer to the ffi_cif initialized specifically + for this function. + + FN is a pointer to the function you want to call. + + RVALUE is a pointer to a chunk of memory that is to hold the + result of the function call. Currently, it must be + at least one word in size (except for the n32 version + under Irix 6.x, which must be a pointer to an 8 byte + aligned value (a long long). It must also be at least + word aligned (depending on the return type, and the + system's alignment requirements). If RTYPE is + &ffi_type_void, this is ignored. If RVALUE is NULL, + the return value is discarded. + + AVALUES is a vector of void* that point to the memory locations + holding the argument values for a call. + If NARGS is 0, this is ignored. + + +If you are expecting a return value from FN it will have been stored +at RVALUE. + + + + An Example + ---------- + +Here is a trivial example that calls puts() a few times. + + #include + #include + + int main() + { + ffi_cif cif; + ffi_type *args[1]; + void *values[1]; + char *s; + int rc; + + /* Initialize the argument info vectors */ + args[0] = &ffi_type_uint; + values[0] = &s; + + /* Initialize the cif */ + if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, + &ffi_type_uint, args) == FFI_OK) + { + s = "Hello World!"; + ffi_call(&cif, puts, &rc, values); + /* rc now holds the result of the call to puts */ + + /* values holds a pointer to the function's arg, so to + call puts() again all we need to do is change the + value of s */ + s = "This is cool!"; + ffi_call(&cif, puts, &rc, values); + } + + return 0; + } + + + + Aggregate Types + --------------- + +Although libffi has no special support for unions or bit-fields, it is +perfectly happy passing structures back and forth. You must first +describe the structure to libffi by creating a new ffi_type object +for it. Here is the definition of ffi_type: + + typedef struct _ffi_type + { + unsigned size; + short alignment; + short type; + struct _ffi_type **elements; + } ffi_type; + +All structures must have type set to FFI_TYPE_STRUCT. You may set +size and alignment to 0. These will be calculated and reset to the +appropriate values by ffi_prep_cif(). + +elements is a NULL terminated array of pointers to ffi_type objects +that describe the type of the structure elements. These may, in turn, +be structure elements. + +The following example initializes a ffi_type object representing the +tm struct from Linux's time.h: + + struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; + /* Those are for future use. */ + long int __tm_gmtoff__; + __const char *__tm_zone__; + }; + + { + ffi_type tm_type; + ffi_type *tm_type_elements[12]; + int i; + + tm_type.size = tm_type.alignment = 0; + tm_type.elements = &tm_type_elements; + + for (i = 0; i < 9; i++) + tm_type_elements[i] = &ffi_type_sint; + + tm_type_elements[9] = &ffi_type_slong; + tm_type_elements[10] = &ffi_type_pointer; + tm_type_elements[11] = NULL; + + /* tm_type can now be used to represent tm argument types and + return types for ffi_prep_cif() */ + } + + + +Platform Specific Notes +======================= + + Intel x86 + --------- + +There are no known problems with the x86 port. + + Sun SPARC - SunOS 4.1.3 & Solaris 2.x + ------------------------------------- + +You must use GNU Make to build libffi on Sun platforms. + + MIPS - Irix 5.3 & 6.x + --------------------- + +Irix 6.2 and better supports three different calling conventions: o32, +n32 and n64. Currently, libffi only supports both o32 and n32 under +Irix 6.x, but only o32 under Irix 5.3. Libffi will automatically be +configured for whichever calling convention it was built for. + +By default, the configure script will try to build libffi with the GNU +development tools. To build libffi with the SGI development tools, set +the environment variable CC to either "cc -32" or "cc -n32" before +running configure under Irix 6.x (depending on whether you want an o32 +or n32 library), or just "cc" for Irix 5.3. + +With the n32 calling convention, when returning structures smaller +than 16 bytes, be sure to provide an RVALUE that is 8 byte aligned. +Here's one way of forcing this: + + double struct_storage[2]; + my_small_struct *s = (my_small_struct *) struct_storage; + /* Use s for RVALUE */ + +If you don't do this you are liable to get spurious bus errors. + +"long long" values are not supported yet. + +You must use GNU Make to build libffi on SGI platforms. + + ARM - System V ABI + ------------------ + +The ARM port was performed on a NetWinder running ARM Linux ELF +(2.0.31) and gcc 2.8.1. + + + + PowerPC System V ABI + -------------------- + +There are two `System V ABI's which libffi implements for PowerPC. +They differ only in how small structures are returned from functions. + +In the FFI_SYSV version, structures that are 8 bytes or smaller are +returned in registers. This is what GCC does when it is configured +for solaris, and is what the System V ABI I have (dated September +1995) says. + +In the FFI_GCC_SYSV version, all structures are returned the same way: +by passing a pointer as the first argument to the function. This is +what GCC does when it is configured for linux or a generic sysv +target. + +EGCS 1.0.1 (and probably other versions of EGCS/GCC) also has a +inconsistency with the SysV ABI: When a procedure is called with many +floating-point arguments, some of them get put on the stack. They are +all supposed to be stored in double-precision format, even if they are +only single-precision, but EGCS stores single-precision arguments as +single-precision anyway. This causes one test to fail (the `many +arguments' test). + + +What's With The Crazy Comments? +=============================== + +You might notice a number of cryptic comments in the code, delimited +by /*@ and @*/. These are annotations read by the program LCLint, a +tool for statically checking C programs. You can read all about it at +. + + +History +======= + +1.20 Oct-5-98 + Raffaele Sena produces ARM port. + +1.19 Oct-5-98 + Fixed x86 long double and long long return support. + m68k bug fixes from Andreas Schwab. + Patch for DU assembler compatibility for the Alpha from Richard + Henderson. + +1.18 Apr-17-98 + Bug fixes and MIPS configuration changes. + +1.17 Feb-24-98 + Bug fixes and m68k port from Andreas Schwab. PowerPC port from + Geoffrey Keating. Various bug x86, Sparc and MIPS bug fixes. + +1.16 Feb-11-98 + Richard Henderson produces Alpha port. + +1.15 Dec-4-97 + Fixed an n32 ABI bug. New libtool, auto* support. + +1.14 May-13-97 + libtool is now used to generate shared and static libraries. + Fixed a minor portability problem reported by Russ McManus + . + +1.13 Dec-2-96 + Added --enable-purify-safety to keep Purify from complaining + about certain low level code. + Sparc fix for calling functions with < 6 args. + Linux x86 a.out fix. + +1.12 Nov-22-96 + Added missing ffi_type_void, needed for supporting void return + types. Fixed test case for non MIPS machines. Cygnus Support + is now Cygnus Solutions. + +1.11 Oct-30-96 + Added notes about GNU make. + +1.10 Oct-29-96 + Added configuration fix for non GNU compilers. + +1.09 Oct-29-96 + Added --enable-debug configure switch. Clean-ups based on LCLint + feedback. ffi_mips.h is always installed. Many configuration + fixes. Fixed ffitest.c for sparc builds. + +1.08 Oct-15-96 + Fixed n32 problem. Many clean-ups. + +1.07 Oct-14-96 + Gordon Irlam rewrites v8.S again. Bug fixes. + +1.06 Oct-14-96 + Gordon Irlam improved the sparc port. + +1.05 Oct-14-96 + Interface changes based on feedback. + +1.04 Oct-11-96 + Sparc port complete (modulo struct passing bug). + +1.03 Oct-10-96 + Passing struct args, and returning struct values works for + all architectures/calling conventions. Expanded tests. + +1.02 Oct-9-96 + Added SGI n32 support. Fixed bugs in both o32 and Linux support. + Added "make test". + +1.01 Oct-8-96 + Fixed float passing bug in mips version. Restructured some + of the code. Builds cleanly with SGI tools. + +1.00 Oct-7-96 + First release. No public announcement. + + +Authors & Credits +================= + +libffi was written by Anthony Green . + +Portions of libffi were derived from Gianni Mariani's free gencall +library for Silicon Graphics machines. + +The closure mechanism was designed and implemented by Kresten Krab +Thorup. + +The Sparc port was derived from code contributed by the fine folks at +Visible Decisions Inc . Further enhancements were +made by Gordon Irlam at Cygnus Solutions . + +The Alpha port was written by Richard Henderson at Cygnus Solutions. + +Andreas Schwab ported libffi to m68k Linux and provided a number of +bug fixes. + +Geoffrey Keating ported libffi to the PowerPC. + +Raffaele Sena ported libffi to the ARM. + +Jesper Skov and Andrew Haley both did more than their fair share of +stepping through the code and tracking down bugs. + +Thanks also to Tom Tromey for bug fixes and configuration help. + +Thanks to Jim Blandy, who provided some useful feedback on the libffi +interface. + +If you have a problem, or have found a bug, please send a note to +green@cygnus.com. diff --git a/c/libffi_msvc/README.ctypes b/c/libffi_msvc/README.ctypes new file mode 100644 index 0000000..17e8a40 --- /dev/null +++ b/c/libffi_msvc/README.ctypes @@ -0,0 +1,7 @@ +The purpose is to hack the libffi sources so that they can be compiled +with MSVC, and to extend them so that they have the features I need +for ctypes. + +I retrieved the libffi sources from the gcc cvs repository on +2004-01-27. Then I did 'configure' in a 'build' subdirectory on a x86 +linux system, and copied the files I found useful. diff --git a/c/libffi_msvc/ffi.c b/c/libffi_msvc/ffi.c new file mode 100644 index 0000000..836f171 --- /dev/null +++ b/c/libffi_msvc/ffi.c @@ -0,0 +1,486 @@ +/* ----------------------------------------------------------------------- + ffi.c - Copyright (c) 1996, 1998, 1999, 2001 Red Hat, Inc. + Copyright (c) 2002 Ranjit Mathew + Copyright (c) 2002 Bo Thorsen + Copyright (c) 2002 Roger Sayle + + x86 Foreign Function Interface + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#include +#include + +#include + +/* ffi_prep_args is called by the assembly routine once stack space + has been allocated for the function's arguments */ + +extern void Py_FatalError(const char *msg); + +/*@-exportheader@*/ +void ffi_prep_args(char *stack, extended_cif *ecif) +/*@=exportheader@*/ +{ + register unsigned int i; + register void **p_argv; + register char *argp; + register ffi_type **p_arg; + + argp = stack; + if (ecif->cif->flags == FFI_TYPE_STRUCT) + { + *(void **) argp = ecif->rvalue; + argp += sizeof(void *); + } + + p_argv = ecif->avalue; + + for (i = ecif->cif->nargs, p_arg = ecif->cif->arg_types; + i != 0; + i--, p_arg++) + { + size_t z; + + /* Align if necessary */ + if ((sizeof(void *) - 1) & (size_t) argp) + argp = (char *) ALIGN(argp, sizeof(void *)); + + z = (*p_arg)->size; + if (z < sizeof(int)) + { + z = sizeof(int); + switch ((*p_arg)->type) + { + case FFI_TYPE_SINT8: + *(signed int *) argp = (signed int)*(SINT8 *)(* p_argv); + break; + + case FFI_TYPE_UINT8: + *(unsigned int *) argp = (unsigned int)*(UINT8 *)(* p_argv); + break; + + case FFI_TYPE_SINT16: + *(signed int *) argp = (signed int)*(SINT16 *)(* p_argv); + break; + + case FFI_TYPE_UINT16: + *(unsigned int *) argp = (unsigned int)*(UINT16 *)(* p_argv); + break; + + case FFI_TYPE_SINT32: + *(signed int *) argp = (signed int)*(SINT32 *)(* p_argv); + break; + + case FFI_TYPE_UINT32: + *(unsigned int *) argp = (unsigned int)*(UINT32 *)(* p_argv); + break; + + case FFI_TYPE_STRUCT: + *(unsigned int *) argp = (unsigned int)*(UINT32 *)(* p_argv); + break; + + default: + FFI_ASSERT(0); + } + } +#ifdef _WIN64 + else if (z > 8) + { + /* On Win64, if a single argument takes more than 8 bytes, + then it is always passed by reference. */ + *(void **)argp = *p_argv; + z = 8; + } +#endif + else + { + memcpy(argp, *p_argv, z); + } + p_argv++; + argp += z; + } + + if (argp - stack > (long)ecif->cif->bytes) + { + Py_FatalError("FFI BUG: not enough stack space for arguments"); + } + return; +} + +/* Perform machine dependent cif processing */ +ffi_status ffi_prep_cif_machdep(ffi_cif *cif) +{ + /* Set the return type flag */ + switch (cif->rtype->type) + { + case FFI_TYPE_VOID: + case FFI_TYPE_SINT64: + case FFI_TYPE_FLOAT: + case FFI_TYPE_DOUBLE: + case FFI_TYPE_LONGDOUBLE: + cif->flags = (unsigned) cif->rtype->type; + break; + + case FFI_TYPE_STRUCT: + /* MSVC returns small structures in registers. Put in cif->flags + the value FFI_TYPE_STRUCT only if the structure is big enough; + otherwise, put the 4- or 8-bytes integer type. */ + if (cif->rtype->size <= 4) + cif->flags = FFI_TYPE_INT; + else if (cif->rtype->size <= 8) + cif->flags = FFI_TYPE_SINT64; + else + cif->flags = FFI_TYPE_STRUCT; + break; + + case FFI_TYPE_UINT64: +#ifdef _WIN64 + case FFI_TYPE_POINTER: +#endif + cif->flags = FFI_TYPE_SINT64; + break; + + default: + cif->flags = FFI_TYPE_INT; + break; + } + + return FFI_OK; +} + +#ifdef _WIN32 +extern int +ffi_call_x86(void (*)(char *, extended_cif *), + /*@out@*/ extended_cif *, + unsigned, unsigned, + /*@out@*/ unsigned *, + void (*fn)()); +#endif + +#ifdef _WIN64 +extern int +ffi_call_AMD64(void (*)(char *, extended_cif *), + /*@out@*/ extended_cif *, + unsigned, unsigned, + /*@out@*/ unsigned *, + void (*fn)()); +#endif + +int +ffi_call(/*@dependent@*/ ffi_cif *cif, + void (*fn)(), + /*@out@*/ void *rvalue, + /*@dependent@*/ void **avalue) +{ + extended_cif ecif; + + ecif.cif = cif; + ecif.avalue = avalue; + + /* If the return value is a struct and we don't have a return */ + /* value address then we need to make one */ + + if ((rvalue == NULL) && + (cif->flags == FFI_TYPE_STRUCT)) + { + /*@-sysunrecog@*/ + ecif.rvalue = alloca(cif->rtype->size); + /*@=sysunrecog@*/ + } + else + ecif.rvalue = rvalue; + + + switch (cif->abi) + { +#if !defined(_WIN64) + case FFI_SYSV: + case FFI_STDCALL: + return ffi_call_x86(ffi_prep_args, &ecif, cif->bytes, + cif->flags, ecif.rvalue, fn); + break; +#else + case FFI_SYSV: + /*@-usedef@*/ + return ffi_call_AMD64(ffi_prep_args, &ecif, cif->bytes, + cif->flags, ecif.rvalue, fn); + /*@=usedef@*/ + break; +#endif + + default: + FFI_ASSERT(0); + break; + } + return -1; /* theller: Hrm. */ +} + + +/** private members **/ + +static void ffi_prep_incoming_args_SYSV (char *stack, void **ret, + void** args, ffi_cif* cif); +/* This function is jumped to by the trampoline */ + +#ifdef _WIN64 +void * +#else +static void __fastcall +#endif +ffi_closure_SYSV (ffi_closure *closure, char *argp) +{ + // this is our return value storage + long double res; + + // our various things... + ffi_cif *cif; + void **arg_area; + unsigned short rtype; + void *resp = (void*)&res; + void *args = argp + sizeof(void *); + + cif = closure->cif; + arg_area = (void**) alloca (cif->nargs * sizeof (void*)); + + /* this call will initialize ARG_AREA, such that each + * element in that array points to the corresponding + * value on the stack; and if the function returns + * a structure, it will re-set RESP to point to the + * structure return address. */ + + ffi_prep_incoming_args_SYSV(args, (void**)&resp, arg_area, cif); + + (closure->fun) (cif, resp, arg_area, closure->user_data); + + rtype = cif->flags; + +#if defined(_WIN32) && !defined(_WIN64) +#ifdef _MSC_VER + /* now, do a generic return based on the value of rtype */ + if (rtype == FFI_TYPE_INT) + { + _asm mov eax, resp ; + _asm mov eax, [eax] ; + } + else if (rtype == FFI_TYPE_FLOAT) + { + _asm mov eax, resp ; + _asm fld DWORD PTR [eax] ; +// asm ("flds (%0)" : : "r" (resp) : "st" ); + } + else if (rtype == FFI_TYPE_DOUBLE) + { + _asm mov eax, resp ; + _asm fld QWORD PTR [eax] ; +// asm ("fldl (%0)" : : "r" (resp) : "st", "st(1)" ); + } + else if (rtype == FFI_TYPE_LONGDOUBLE) + { +// asm ("fldt (%0)" : : "r" (resp) : "st", "st(1)" ); + } + else if (rtype == FFI_TYPE_SINT64) + { + _asm mov edx, resp ; + _asm mov eax, [edx] ; + _asm mov edx, [edx + 4] ; +// asm ("movl 0(%0),%%eax;" +// "movl 4(%0),%%edx" +// : : "r"(resp) +// : "eax", "edx"); + } +#else + /* now, do a generic return based on the value of rtype */ + if (rtype == FFI_TYPE_INT) + { + asm ("movl (%0),%%eax" : : "r" (resp) : "eax"); + } + else if (rtype == FFI_TYPE_FLOAT) + { + asm ("flds (%0)" : : "r" (resp) : "st" ); + } + else if (rtype == FFI_TYPE_DOUBLE) + { + asm ("fldl (%0)" : : "r" (resp) : "st", "st(1)" ); + } + else if (rtype == FFI_TYPE_LONGDOUBLE) + { + asm ("fldt (%0)" : : "r" (resp) : "st", "st(1)" ); + } + else if (rtype == FFI_TYPE_SINT64) + { + asm ("movl 0(%0),%%eax;" + "movl 4(%0),%%edx" + : : "r"(resp) + : "eax", "edx"); + } +#endif +#endif + +#ifdef _WIN64 + /* The result is returned in rax. This does the right thing for + result types except for floats; we have to 'mov xmm0, rax' in the + caller to correct this. + */ + return *(void **)resp; +#endif +} + +/*@-exportheader@*/ +static void +ffi_prep_incoming_args_SYSV(char *stack, void **rvalue, + void **avalue, ffi_cif *cif) +/*@=exportheader@*/ +{ + register unsigned int i; + register void **p_argv; + register char *argp; + register ffi_type **p_arg; + + argp = stack; + + if ( cif->flags == FFI_TYPE_STRUCT ) { + *rvalue = *(void **) argp; + argp += 4; + } + + p_argv = avalue; + + for (i = cif->nargs, p_arg = cif->arg_types; (i != 0); i--, p_arg++) + { + size_t z; + + /* Align if necessary */ + if ((sizeof(char *) - 1) & (size_t) argp) { + argp = (char *) ALIGN(argp, sizeof(char*)); + } + + z = (*p_arg)->size; + + /* because we're little endian, this is what it turns into. */ + +#ifdef _WIN64 + if (z > 8) + { + /* On Win64, if a single argument takes more than 8 bytes, + then it is always passed by reference. */ + *p_argv = *((void**) argp); + z = 8; + } + else +#endif + *p_argv = (void*) argp; + + p_argv++; + argp += z; + } + + return; +} + +/* the cif must already be prep'ed */ +extern void ffi_closure_OUTER(); + +ffi_status +ffi_prep_closure_loc (ffi_closure* closure, + ffi_cif* cif, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data, + void *codeloc) +{ + short bytes; + char *tramp; +#ifdef _WIN64 + int mask = 0; +#endif + FFI_ASSERT (cif->abi == FFI_SYSV); + + if (cif->abi == FFI_SYSV) + bytes = 0; +#if !defined(_WIN64) + else if (cif->abi == FFI_STDCALL) + bytes = cif->bytes; +#endif + else + return FFI_BAD_ABI; + + tramp = &closure->tramp[0]; + +#define BYTES(text) memcpy(tramp, text, sizeof(text)), tramp += sizeof(text)-1 +#define POINTER(x) *(void**)tramp = (void*)(x), tramp += sizeof(void*) +#define SHORT(x) *(short*)tramp = x, tramp += sizeof(short) +#define INT(x) *(int*)tramp = x, tramp += sizeof(int) + +#ifdef _WIN64 + if (cif->nargs >= 1 && + (cif->arg_types[0]->type == FFI_TYPE_FLOAT + || cif->arg_types[0]->type == FFI_TYPE_DOUBLE)) + mask |= 1; + if (cif->nargs >= 2 && + (cif->arg_types[1]->type == FFI_TYPE_FLOAT + || cif->arg_types[1]->type == FFI_TYPE_DOUBLE)) + mask |= 2; + if (cif->nargs >= 3 && + (cif->arg_types[2]->type == FFI_TYPE_FLOAT + || cif->arg_types[2]->type == FFI_TYPE_DOUBLE)) + mask |= 4; + if (cif->nargs >= 4 && + (cif->arg_types[3]->type == FFI_TYPE_FLOAT + || cif->arg_types[3]->type == FFI_TYPE_DOUBLE)) + mask |= 8; + + /* 41 BB ---- mov r11d,mask */ + BYTES("\x41\xBB"); INT(mask); + + /* 48 B8 -------- mov rax, closure */ + BYTES("\x48\xB8"); POINTER(closure); + + /* 49 BA -------- mov r10, ffi_closure_OUTER */ + BYTES("\x49\xBA"); POINTER(ffi_closure_OUTER); + + /* 41 FF E2 jmp r10 */ + BYTES("\x41\xFF\xE2"); + +#else + + /* mov ecx, closure */ + BYTES("\xb9"); POINTER(closure); + + /* mov edx, esp */ + BYTES("\x8b\xd4"); + + /* call ffi_closure_SYSV */ + BYTES("\xe8"); POINTER((char*)&ffi_closure_SYSV - (tramp + 4)); + + /* ret bytes */ + BYTES("\xc2"); + SHORT(bytes); + +#endif + + if (tramp - &closure->tramp[0] > FFI_TRAMPOLINE_SIZE) + Py_FatalError("FFI_TRAMPOLINE_SIZE too small in " __FILE__); + + closure->cif = cif; + closure->user_data = user_data; + closure->fun = fun; + return FFI_OK; +} diff --git a/c/libffi_msvc/ffi.h b/c/libffi_msvc/ffi.h new file mode 100644 index 0000000..97cdb59 --- /dev/null +++ b/c/libffi_msvc/ffi.h @@ -0,0 +1,322 @@ +/* -----------------------------------------------------------------*-C-*- + libffi 2.00-beta - Copyright (c) 1996-2003 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + ----------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------- + The basic API is described in the README file. + + The raw API is designed to bypass some of the argument packing + and unpacking on architectures for which it can be avoided. + + The closure API allows interpreted functions to be packaged up + inside a C function pointer, so that they can be called as C functions, + with no understanding on the client side that they are interpreted. + It can also be used in other cases in which it is necessary to package + up a user specified parameter and a function pointer as a single + function pointer. + + The closure API must be implemented in order to get its functionality, + e.g. for use by gij. Routines are provided to emulate the raw API + if the underlying platform doesn't allow faster implementation. + + More details on the raw and cloure API can be found in: + + http://gcc.gnu.org/ml/java/1999-q3/msg00138.html + + and + + http://gcc.gnu.org/ml/java/1999-q3/msg00174.html + -------------------------------------------------------------------- */ + +#ifndef LIBFFI_H +#define LIBFFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Specify which architecture libffi is configured for. */ +//XXX #define X86 + +/* ---- System configuration information --------------------------------- */ + +#include + +#ifndef LIBFFI_ASM + +#include +#include + +/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). + But we can find it either under the correct ANSI name, or under GNU + C's internal name. */ +#ifdef LONG_LONG_MAX +# define FFI_LONG_LONG_MAX LONG_LONG_MAX +#else +# ifdef LLONG_MAX +# define FFI_LONG_LONG_MAX LLONG_MAX +# else +# ifdef __GNUC__ +# define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ +# endif +# ifdef _MSC_VER +# define FFI_LONG_LONG_MAX _I64_MAX +# endif +# endif +#endif + +#if SCHAR_MAX == 127 +# define ffi_type_uchar ffi_type_uint8 +# define ffi_type_schar ffi_type_sint8 +#else + #error "char size not supported" +#endif + +#if SHRT_MAX == 32767 +# define ffi_type_ushort ffi_type_uint16 +# define ffi_type_sshort ffi_type_sint16 +#elif SHRT_MAX == 2147483647 +# define ffi_type_ushort ffi_type_uint32 +# define ffi_type_sshort ffi_type_sint32 +#else + #error "short size not supported" +#endif + +#if INT_MAX == 32767 +# define ffi_type_uint ffi_type_uint16 +# define ffi_type_sint ffi_type_sint16 +#elif INT_MAX == 2147483647 +# define ffi_type_uint ffi_type_uint32 +# define ffi_type_sint ffi_type_sint32 +#elif INT_MAX == 9223372036854775807 +# define ffi_type_uint ffi_type_uint64 +# define ffi_type_sint ffi_type_sint64 +#else + #error "int size not supported" +#endif + +#define ffi_type_ulong ffi_type_uint64 +#define ffi_type_slong ffi_type_sint64 +#if LONG_MAX == 2147483647 +# if FFI_LONG_LONG_MAX != 9223372036854775807 + #error "no 64-bit data type supported" +# endif +#elif LONG_MAX != 9223372036854775807 + #error "long size not supported" +#endif + +/* The closure code assumes that this works on pointers, i.e. a size_t */ +/* can hold a pointer. */ + +typedef struct _ffi_type +{ + size_t size; + unsigned short alignment; + unsigned short type; + /*@null@*/ struct _ffi_type **elements; +} ffi_type; + +/* These are defined in types.c */ +extern ffi_type ffi_type_void; +extern ffi_type ffi_type_uint8; +extern ffi_type ffi_type_sint8; +extern ffi_type ffi_type_uint16; +extern ffi_type ffi_type_sint16; +extern ffi_type ffi_type_uint32; +extern ffi_type ffi_type_sint32; +extern ffi_type ffi_type_uint64; +extern ffi_type ffi_type_sint64; +extern ffi_type ffi_type_float; +extern ffi_type ffi_type_double; +extern ffi_type ffi_type_longdouble; +extern ffi_type ffi_type_pointer; + + +typedef enum { + FFI_OK = 0, + FFI_BAD_TYPEDEF, + FFI_BAD_ABI +} ffi_status; + +typedef unsigned FFI_TYPE; + +typedef struct { + ffi_abi abi; + unsigned nargs; + /*@dependent@*/ ffi_type **arg_types; + /*@dependent@*/ ffi_type *rtype; + unsigned bytes; + unsigned flags; +#ifdef FFI_EXTRA_CIF_FIELDS + FFI_EXTRA_CIF_FIELDS; +#endif +} ffi_cif; + +/* ---- Definitions for the raw API -------------------------------------- */ + +#ifdef _WIN64 +#define FFI_SIZEOF_ARG 8 +#else +#define FFI_SIZEOF_ARG 4 +#endif + +typedef union { + ffi_sarg sint; + ffi_arg uint; + float flt; + char data[FFI_SIZEOF_ARG]; + void* ptr; +} ffi_raw; + +void ffi_raw_call (/*@dependent@*/ ffi_cif *cif, + void (*fn)(), + /*@out@*/ void *rvalue, + /*@dependent@*/ ffi_raw *avalue); + +void ffi_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw); +void ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args); +size_t ffi_raw_size (ffi_cif *cif); + +/* This is analogous to the raw API, except it uses Java parameter */ +/* packing, even on 64-bit machines. I.e. on 64-bit machines */ +/* longs and doubles are followed by an empty 64-bit word. */ + +void ffi_java_raw_call (/*@dependent@*/ ffi_cif *cif, + void (*fn)(), + /*@out@*/ void *rvalue, + /*@dependent@*/ ffi_raw *avalue); + +void ffi_java_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw); +void ffi_java_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args); +size_t ffi_java_raw_size (ffi_cif *cif); + +/* ---- Definitions for closures ----------------------------------------- */ + +#if FFI_CLOSURES + +typedef struct { + char tramp[FFI_TRAMPOLINE_SIZE]; + ffi_cif *cif; + void (*fun)(ffi_cif*,void*,void**,void*); + void *user_data; +} ffi_closure; + +void ffi_closure_free(void *); +void *ffi_closure_alloc (size_t size, void **code); + +ffi_status +ffi_prep_closure_loc (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data, + void *codeloc); + +/* AR: for cffi we need the following API, and not the _loc version */ +#define ffi_prep_closure(a,b,c,d) ffi_prep_closure_loc(a,b,c,d,a) + +typedef struct { + char tramp[FFI_TRAMPOLINE_SIZE]; + + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_raw*,void*); + void *user_data; + +} ffi_raw_closure; + +ffi_status +ffi_prep_raw_closure (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data); + +ffi_status +ffi_prep_java_raw_closure (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data); + +#endif /* FFI_CLOSURES */ + +/* ---- Public interface definition -------------------------------------- */ + +ffi_status ffi_prep_cif(/*@out@*/ /*@partial@*/ ffi_cif *cif, + ffi_abi abi, + unsigned int nargs, + /*@dependent@*/ /*@out@*/ /*@partial@*/ ffi_type *rtype, + /*@dependent@*/ ffi_type **atypes); + +int +ffi_call(/*@dependent@*/ ffi_cif *cif, + void (*fn)(), + /*@out@*/ void *rvalue, + /*@dependent@*/ void **avalue); + +/* Useful for eliminating compiler warnings */ +#define FFI_FN(f) ((void (*)())f) + +/* ---- Definitions shared with assembly code ---------------------------- */ + +#endif + +/* If these change, update src/mips/ffitarget.h. */ +#define FFI_TYPE_VOID 0 +#define FFI_TYPE_INT 1 +#define FFI_TYPE_FLOAT 2 +#define FFI_TYPE_DOUBLE 3 +#if 1 +#define FFI_TYPE_LONGDOUBLE 4 +#else +#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE +#endif +#define FFI_TYPE_UINT8 5 +#define FFI_TYPE_SINT8 6 +#define FFI_TYPE_UINT16 7 +#define FFI_TYPE_SINT16 8 +#define FFI_TYPE_UINT32 9 +#define FFI_TYPE_SINT32 10 +#define FFI_TYPE_UINT64 11 +#define FFI_TYPE_SINT64 12 +#define FFI_TYPE_STRUCT 13 +#define FFI_TYPE_POINTER 14 + +/* This should always refer to the last type code (for sanity checks) */ +#define FFI_TYPE_LAST FFI_TYPE_POINTER + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/c/libffi_msvc/ffi_common.h b/c/libffi_msvc/ffi_common.h new file mode 100644 index 0000000..43fb83b --- /dev/null +++ b/c/libffi_msvc/ffi_common.h @@ -0,0 +1,77 @@ +/* ----------------------------------------------------------------------- + ffi_common.h - Copyright (c) 1996 Red Hat, Inc. + + Common internal definitions and macros. Only necessary for building + libffi. + ----------------------------------------------------------------------- */ + +#ifndef FFI_COMMON_H +#define FFI_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* Check for the existence of memcpy. */ +#if STDC_HEADERS +# include +#else +# ifndef HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#if defined(FFI_DEBUG) +#include +#endif + +#ifdef FFI_DEBUG +/*@exits@*/ void ffi_assert(/*@temp@*/ char *expr, /*@temp@*/ char *file, int line); +void ffi_stop_here(void); +void ffi_type_test(/*@temp@*/ /*@out@*/ ffi_type *a, /*@temp@*/ char *file, int line); + +#define FFI_ASSERT(x) ((x) ? (void)0 : ffi_assert(#x, __FILE__,__LINE__)) +#define FFI_ASSERT_AT(x, f, l) ((x) ? 0 : ffi_assert(#x, (f), (l))) +#define FFI_ASSERT_VALID_TYPE(x) ffi_type_test (x, __FILE__, __LINE__) +#else +#define FFI_ASSERT(x) +#define FFI_ASSERT_AT(x, f, l) +#define FFI_ASSERT_VALID_TYPE(x) +#endif + +#define ALIGN(v, a) (((((size_t) (v))-1) | ((a)-1))+1) + +/* Perform machine dependent cif processing */ +ffi_status ffi_prep_cif_machdep(ffi_cif *cif); + +/* Extended cif, used in callback from assembly routine */ +typedef struct +{ + /*@dependent@*/ ffi_cif *cif; + /*@dependent@*/ void *rvalue; + /*@dependent@*/ void **avalue; +} extended_cif; + +/* Terse sized type definitions. */ +typedef unsigned int UINT8 __attribute__((__mode__(__QI__))); +typedef signed int SINT8 __attribute__((__mode__(__QI__))); +typedef unsigned int UINT16 __attribute__((__mode__(__HI__))); +typedef signed int SINT16 __attribute__((__mode__(__HI__))); +typedef unsigned int UINT32 __attribute__((__mode__(__SI__))); +typedef signed int SINT32 __attribute__((__mode__(__SI__))); +typedef unsigned int UINT64 __attribute__((__mode__(__DI__))); +typedef signed int SINT64 __attribute__((__mode__(__DI__))); + +typedef float FLOAT32; + + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/c/libffi_msvc/fficonfig.h b/c/libffi_msvc/fficonfig.h new file mode 100644 index 0000000..c14f653 --- /dev/null +++ b/c/libffi_msvc/fficonfig.h @@ -0,0 +1,96 @@ +/* fficonfig.h. Originally created by configure, now hand_maintained for MSVC. */ + +/* fficonfig.h. Generated automatically by configure. */ +/* fficonfig.h.in. Generated automatically from configure.in by autoheader. */ + +/* Define this for MSVC, but not for mingw32! */ +#ifdef _MSC_VER +#define __attribute__(x) /* */ +#endif +#define alloca _alloca + +/*----------------------------------------------------------------*/ + +/* Define if using alloca.c. */ +/* #undef C_ALLOCA */ + +/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. + This function is required for alloca.c support on those systems. */ +/* #undef CRAY_STACKSEG_END */ + +/* Define if you have alloca, as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define if you have and it should be used (not on Ultrix). */ +/* #define HAVE_ALLOCA_H 1 */ + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at run-time. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown + */ +/* #undef STACK_DIRECTION */ + +/* Define if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if you have the memcpy function. */ +#define HAVE_MEMCPY 1 + +/* Define if read-only mmap of a plain file works. */ +//#define HAVE_MMAP_FILE 1 + +/* Define if mmap of /dev/zero works. */ +//#define HAVE_MMAP_DEV_ZERO 1 + +/* Define if mmap with MAP_ANON(YMOUS) works. */ +//#define HAVE_MMAP_ANON 1 + +/* The number of bytes in type double */ +#define SIZEOF_DOUBLE 8 + +/* The number of bytes in type long double */ +#define SIZEOF_LONG_DOUBLE 12 + +/* Define if you have the long double type and it is bigger than a double */ +#define HAVE_LONG_DOUBLE 1 + +/* whether byteorder is bigendian */ +/* #undef WORDS_BIGENDIAN */ + +/* Define if the host machine stores words of multi-word integers in + big-endian order. */ +/* #undef HOST_WORDS_BIG_ENDIAN */ + +/* 1234 = LIL_ENDIAN, 4321 = BIGENDIAN */ +#define BYTEORDER 1234 + +/* Define if your assembler and linker support unaligned PC relative relocs. */ +/* #undef HAVE_AS_SPARC_UA_PCREL */ + +/* Define if your assembler supports .register. */ +/* #undef HAVE_AS_REGISTER_PSEUDO_OP */ + +/* Define if .eh_frame sections should be read-only. */ +/* #undef HAVE_RO_EH_FRAME */ + +/* Define to the flags needed for the .section .eh_frame directive. */ +/* #define EH_FRAME_FLAGS "aw" */ + +/* Define to the flags needed for the .section .eh_frame directive. */ +/* #define EH_FRAME_FLAGS "aw" */ + +/* Define this if you want extra debugging. */ +/* #undef FFI_DEBUG */ + +/* Define this is you do not want support for aggregate types. */ +/* #undef FFI_NO_STRUCTS */ + +/* Define this is you do not want support for the raw API. */ +/* #undef FFI_NO_RAW_API */ + +/* Define this if you are using Purify and want to suppress spurious messages. */ +/* #undef USING_PURIFY */ + diff --git a/c/libffi_msvc/ffitarget.h b/c/libffi_msvc/ffitarget.h new file mode 100644 index 0000000..85f5ee8 --- /dev/null +++ b/c/libffi_msvc/ffitarget.h @@ -0,0 +1,85 @@ +/* -----------------------------------------------------------------*-C-*- + ffitarget.h - Copyright (c) 1996-2003 Red Hat, Inc. + Target configuration macros for x86 and x86-64. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + ----------------------------------------------------------------------- */ + +#ifndef LIBFFI_TARGET_H +#define LIBFFI_TARGET_H + +/* ---- System specific configurations ----------------------------------- */ + +#if defined (X86_64) && defined (__i386__) +#undef X86_64 +#define X86 +#endif + +/* ---- Generic type definitions ----------------------------------------- */ + +#ifndef LIBFFI_ASM +#ifndef _WIN64 +typedef unsigned long ffi_arg; +#else +typedef unsigned __int64 ffi_arg; +#endif +typedef signed long ffi_sarg; + +typedef enum ffi_abi { + FFI_FIRST_ABI = 0, + + /* ---- Intel x86 Win32 ---------- */ + FFI_SYSV, +#ifndef _WIN64 + FFI_STDCALL, +#endif + /* TODO: Add fastcall support for the sake of completeness */ + FFI_DEFAULT_ABI = FFI_SYSV, + + /* ---- Intel x86 and AMD x86-64 - */ +/* #if !defined(X86_WIN32) && (defined(__i386__) || defined(__x86_64__)) */ +/* FFI_SYSV, */ +/* FFI_UNIX64,*/ /* Unix variants all use the same ABI for x86-64 */ +/* #ifdef __i386__ */ +/* FFI_DEFAULT_ABI = FFI_SYSV, */ +/* #else */ +/* FFI_DEFAULT_ABI = FFI_UNIX64, */ +/* #endif */ +/* #endif */ + + FFI_LAST_ABI = FFI_DEFAULT_ABI + 1 +} ffi_abi; +#endif + +/* ---- Definitions for closures ----------------------------------------- */ + +#define FFI_CLOSURES 1 + +#ifdef _WIN64 +#define FFI_TRAMPOLINE_SIZE 29 +#define FFI_NATIVE_RAW_API 0 +#else +#define FFI_TRAMPOLINE_SIZE 15 +#define FFI_NATIVE_RAW_API 1 /* x86 has native raw api support */ +#endif + +#endif + diff --git a/c/libffi_msvc/prep_cif.c b/c/libffi_msvc/prep_cif.c new file mode 100644 index 0000000..5dacfff --- /dev/null +++ b/c/libffi_msvc/prep_cif.c @@ -0,0 +1,181 @@ +/* ----------------------------------------------------------------------- + prep_cif.c - Copyright (c) 1996, 1998 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#include +#include +#include + + +/* Round up to FFI_SIZEOF_ARG. */ + +#define STACK_ARG_SIZE(x) ALIGN(x, FFI_SIZEOF_ARG) + +/* Perform machine independent initialization of aggregate type + specifications. */ + +static ffi_status initialize_aggregate(/*@out@*/ ffi_type *arg) +{ + ffi_type **ptr; + + FFI_ASSERT(arg != NULL); + + /*@-usedef@*/ + + FFI_ASSERT(arg->elements != NULL); + FFI_ASSERT(arg->size == 0); + FFI_ASSERT(arg->alignment == 0); + + ptr = &(arg->elements[0]); + + while ((*ptr) != NULL) + { + if (((*ptr)->size == 0) && (initialize_aggregate((*ptr)) != FFI_OK)) + return FFI_BAD_TYPEDEF; + + /* Perform a sanity check on the argument type */ + FFI_ASSERT_VALID_TYPE(*ptr); + + arg->size = ALIGN(arg->size, (*ptr)->alignment); + arg->size += (*ptr)->size; + + arg->alignment = (arg->alignment > (*ptr)->alignment) ? + arg->alignment : (*ptr)->alignment; + + ptr++; + } + + /* Structure size includes tail padding. This is important for + structures that fit in one register on ABIs like the PowerPC64 + Linux ABI that right justify small structs in a register. + It's also needed for nested structure layout, for example + struct A { long a; char b; }; struct B { struct A x; char y; }; + should find y at an offset of 2*sizeof(long) and result in a + total size of 3*sizeof(long). */ + arg->size = ALIGN (arg->size, arg->alignment); + + if (arg->size == 0) + return FFI_BAD_TYPEDEF; + else + return FFI_OK; + + /*@=usedef@*/ +} + +/* Perform machine independent ffi_cif preparation, then call + machine dependent routine. */ + +ffi_status ffi_prep_cif(/*@out@*/ /*@partial@*/ ffi_cif *cif, + ffi_abi abi, unsigned int nargs, + /*@dependent@*/ /*@out@*/ /*@partial@*/ ffi_type *rtype, + /*@dependent@*/ ffi_type **atypes) +{ + unsigned bytes = 0; + unsigned int i; + ffi_type **ptr; + + FFI_ASSERT(cif != NULL); + FFI_ASSERT((abi > FFI_FIRST_ABI) && (abi <= FFI_DEFAULT_ABI)); + + cif->abi = abi; + cif->arg_types = atypes; + cif->nargs = nargs; + cif->rtype = rtype; + + cif->flags = 0; + + /* Initialize the return type if necessary */ + /*@-usedef@*/ + if ((cif->rtype->size == 0) && (initialize_aggregate(cif->rtype) != FFI_OK)) + return FFI_BAD_TYPEDEF; + /*@=usedef@*/ + + /* Perform a sanity check on the return type */ + FFI_ASSERT_VALID_TYPE(cif->rtype); + + /* x86-64 and s390 stack space allocation is handled in prep_machdep. */ +#if !defined M68K && !defined __x86_64__ && !defined S390 + /* Make space for the return structure pointer */ + if (cif->rtype->type == FFI_TYPE_STRUCT +#ifdef _WIN32 + && (cif->rtype->size > 8) /* MSVC returns small structs in registers */ +#endif +#ifdef SPARC + && (cif->abi != FFI_V9 || cif->rtype->size > 32) +#endif + ) + bytes = STACK_ARG_SIZE(sizeof(void*)); +#endif + + for (ptr = cif->arg_types, i = cif->nargs; i > 0; i--, ptr++) + { + + /* Initialize any uninitialized aggregate type definitions */ + if (((*ptr)->size == 0) && (initialize_aggregate((*ptr)) != FFI_OK)) + return FFI_BAD_TYPEDEF; + + /* Perform a sanity check on the argument type, do this + check after the initialization. */ + FFI_ASSERT_VALID_TYPE(*ptr); + +#if !defined __x86_64__ && !defined S390 +#ifdef SPARC + if (((*ptr)->type == FFI_TYPE_STRUCT + && ((*ptr)->size > 16 || cif->abi != FFI_V9)) + || ((*ptr)->type == FFI_TYPE_LONGDOUBLE + && cif->abi != FFI_V9)) + bytes += sizeof(void*); + else +#endif + { +#if !defined(_MSC_VER) && !defined(__MINGW32__) + /* Don't know if this is a libffi bug or not. At least on + Windows with MSVC, function call parameters are *not* + aligned in the same way as structure fields are, they are + only aligned in integer boundaries. + + This doesn't do any harm for cdecl functions and closures, + since the caller cleans up the stack, but it is wrong for + stdcall functions where the callee cleans. + */ + + /* Add any padding if necessary */ + if (((*ptr)->alignment - 1) & bytes) + bytes = ALIGN(bytes, (*ptr)->alignment); + +#endif + bytes += STACK_ARG_SIZE((*ptr)->size); + } +#endif + } + +#ifdef _WIN64 + /* Function call needs at least 40 bytes stack size, on win64 AMD64 */ + if (bytes < 40) + bytes = 40; +#endif + + cif->bytes = bytes; + + /* Perform machine dependent cif processing */ + return ffi_prep_cif_machdep(cif); +} diff --git a/c/libffi_msvc/types.c b/c/libffi_msvc/types.c new file mode 100644 index 0000000..4433ac2 --- /dev/null +++ b/c/libffi_msvc/types.c @@ -0,0 +1,104 @@ +/* ----------------------------------------------------------------------- + types.c - Copyright (c) 1996, 1998 Red Hat, Inc. + + Predefined ffi_types needed by libffi. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +#include +#include + +/* Type definitions */ + +#define FFI_INTEGRAL_TYPEDEF(n, s, a, t) ffi_type ffi_type_##n = { s, a, t, NULL } +#define FFI_AGGREGATE_TYPEDEF(n, e) ffi_type ffi_type_##n = { 0, 0, FFI_TYPE_STRUCT, e } + +/* Size and alignment are fake here. They must not be 0. */ +FFI_INTEGRAL_TYPEDEF(void, 1, 1, FFI_TYPE_VOID); + +FFI_INTEGRAL_TYPEDEF(uint8, 1, 1, FFI_TYPE_UINT8); +FFI_INTEGRAL_TYPEDEF(sint8, 1, 1, FFI_TYPE_SINT8); +FFI_INTEGRAL_TYPEDEF(uint16, 2, 2, FFI_TYPE_UINT16); +FFI_INTEGRAL_TYPEDEF(sint16, 2, 2, FFI_TYPE_SINT16); +FFI_INTEGRAL_TYPEDEF(uint32, 4, 4, FFI_TYPE_UINT32); +FFI_INTEGRAL_TYPEDEF(sint32, 4, 4, FFI_TYPE_SINT32); +FFI_INTEGRAL_TYPEDEF(float, 4, 4, FFI_TYPE_FLOAT); + +#if defined ALPHA || defined SPARC64 || defined X86_64 || defined S390X \ + || defined IA64 || defined _WIN64 + +FFI_INTEGRAL_TYPEDEF(pointer, 8, 8, FFI_TYPE_POINTER); + +#else + +FFI_INTEGRAL_TYPEDEF(pointer, 4, 4, FFI_TYPE_POINTER); + +#endif + +#if defined X86 || defined X86_WIN32 || defined ARM || defined M68K + +FFI_INTEGRAL_TYPEDEF(uint64, 8, 4, FFI_TYPE_UINT64); +FFI_INTEGRAL_TYPEDEF(sint64, 8, 4, FFI_TYPE_SINT64); + +#elif defined SH + +FFI_INTEGRAL_TYPEDEF(uint64, 8, 4, FFI_TYPE_UINT64); +FFI_INTEGRAL_TYPEDEF(sint64, 8, 4, FFI_TYPE_SINT64); + +#else + +FFI_INTEGRAL_TYPEDEF(uint64, 8, 8, FFI_TYPE_UINT64); +FFI_INTEGRAL_TYPEDEF(sint64, 8, 8, FFI_TYPE_SINT64); + +#endif + + +#if defined X86 || defined X86_WIN32 || defined M68K + +FFI_INTEGRAL_TYPEDEF(double, 8, 4, FFI_TYPE_DOUBLE); +FFI_INTEGRAL_TYPEDEF(longdouble, 12, 4, FFI_TYPE_LONGDOUBLE); + +#elif defined ARM || defined SH || defined POWERPC_AIX || defined POWERPC_DARWIN + +FFI_INTEGRAL_TYPEDEF(double, 8, 4, FFI_TYPE_DOUBLE); +FFI_INTEGRAL_TYPEDEF(longdouble, 8, 4, FFI_TYPE_LONGDOUBLE); + +#elif defined SPARC + +FFI_INTEGRAL_TYPEDEF(double, 8, 8, FFI_TYPE_DOUBLE); +#ifdef SPARC64 +FFI_INTEGRAL_TYPEDEF(longdouble, 16, 16, FFI_TYPE_LONGDOUBLE); +#else +FFI_INTEGRAL_TYPEDEF(longdouble, 16, 8, FFI_TYPE_LONGDOUBLE); +#endif + +#elif defined X86_64 + +FFI_INTEGRAL_TYPEDEF(double, 8, 8, FFI_TYPE_DOUBLE); +FFI_INTEGRAL_TYPEDEF(longdouble, 16, 16, FFI_TYPE_LONGDOUBLE); + +#else + +FFI_INTEGRAL_TYPEDEF(double, 8, 8, FFI_TYPE_DOUBLE); +FFI_INTEGRAL_TYPEDEF(longdouble, 8, 8, FFI_TYPE_LONGDOUBLE); + +#endif + diff --git a/c/libffi_msvc/win32.c b/c/libffi_msvc/win32.c new file mode 100644 index 0000000..d1149a8 --- /dev/null +++ b/c/libffi_msvc/win32.c @@ -0,0 +1,162 @@ +/* ----------------------------------------------------------------------- + win32.S - Copyright (c) 1996, 1998, 2001, 2002 Red Hat, Inc. + Copyright (c) 2001 John Beniton + Copyright (c) 2002 Ranjit Mathew + + + X86 Foreign Function Interface + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL CYGNUS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + ----------------------------------------------------------------------- */ + +/* theller: almost verbatim translation from gas syntax to MSVC inline + assembler code. */ + +/* theller: ffi_call_x86 now returns an integer - the difference of the stack + pointer before and after the function call. If everything is ok, zero is + returned. If stdcall functions are passed the wrong number of arguments, + the difference will be nonzero. */ + +#include +#include + +__declspec(naked) int +ffi_call_x86(void (* prepfunc)(char *, extended_cif *), /* 8 */ + extended_cif *ecif, /* 12 */ + unsigned bytes, /* 16 */ + unsigned flags, /* 20 */ + unsigned *rvalue, /* 24 */ + void (*fn)()) /* 28 */ +{ + _asm { + push ebp + mov ebp, esp + + push esi // NEW: this register must be preserved across function calls +// XXX SAVE ESP NOW! + mov esi, esp // save stack pointer before the call + +// Make room for all of the new args. + mov ecx, [ebp+16] + sub esp, ecx // sub esp, bytes + + mov eax, esp + +// Place all of the ffi_prep_args in position + push [ebp + 12] // ecif + push eax + call [ebp + 8] // prepfunc + +// Return stack to previous state and call the function + add esp, 8 +// FIXME: Align the stack to a 128-bit boundary to avoid +// potential performance hits. + call [ebp + 28] + +// Load ecif->cif->abi + mov ecx, [ebp + 12] + mov ecx, [ecx]ecif.cif + mov ecx, [ecx]ecif.cif.abi + + cmp ecx, FFI_STDCALL + je noclean +// STDCALL: Remove the space we pushed for the args + mov ecx, [ebp + 16] + add esp, ecx +// CDECL: Caller has already cleaned the stack +noclean: +// Check that esp has the same value as before! + sub esi, esp + +// Load %ecx with the return type code + mov ecx, [ebp + 20] + +// If the return value pointer is NULL, assume no return value. +/* + Intel asm is weird. We have to explicitely specify 'DWORD PTR' in the nexr instruction, + otherwise only one BYTE will be compared (instead of a DWORD)! + */ + cmp DWORD PTR [ebp + 24], 0 + jne sc_retint + +// Even if there is no space for the return value, we are +// obliged to handle floating-point values. + cmp ecx, FFI_TYPE_FLOAT + jne sc_noretval +// fstp %st(0) + fstp st(0) + + jmp sc_epilogue + +sc_retint: + cmp ecx, FFI_TYPE_INT + jne sc_retfloat +// # Load %ecx with the pointer to storage for the return value + mov ecx, [ebp + 24] + mov [ecx + 0], eax + jmp sc_epilogue + +sc_retfloat: + cmp ecx, FFI_TYPE_FLOAT + jne sc_retdouble +// Load %ecx with the pointer to storage for the return value + mov ecx, [ebp+24] +// fstps (%ecx) + fstp DWORD PTR [ecx] + jmp sc_epilogue + +sc_retdouble: + cmp ecx, FFI_TYPE_DOUBLE + jne sc_retlongdouble +// movl 24(%ebp),%ecx + mov ecx, [ebp+24] + fstp QWORD PTR [ecx] + jmp sc_epilogue + + jmp sc_retlongdouble // avoid warning about unused label +sc_retlongdouble: + cmp ecx, FFI_TYPE_LONGDOUBLE + jne sc_retint64 +// Load %ecx with the pointer to storage for the return value + mov ecx, [ebp+24] +// fstpt (%ecx) + fstp QWORD PTR [ecx] /* XXX ??? */ + jmp sc_epilogue + +sc_retint64: + cmp ecx, FFI_TYPE_SINT64 + jne sc_retstruct +// Load %ecx with the pointer to storage for the return value + mov ecx, [ebp+24] + mov [ecx+0], eax + mov [ecx+4], edx + +sc_retstruct: +// Nothing to do! + +sc_noretval: +sc_epilogue: + mov eax, esi + pop esi // NEW restore: must be preserved across function calls + mov esp, ebp + pop ebp + ret + } +} diff --git a/c/libffi_msvc/win64.asm b/c/libffi_msvc/win64.asm new file mode 100644 index 0000000..301188b --- /dev/null +++ b/c/libffi_msvc/win64.asm @@ -0,0 +1,156 @@ +PUBLIC ffi_call_AMD64 + +EXTRN __chkstk:NEAR +EXTRN ffi_closure_SYSV:NEAR + +_TEXT SEGMENT + +;;; ffi_closure_OUTER will be called with these registers set: +;;; rax points to 'closure' +;;; r11 contains a bit mask that specifies which of the +;;; first four parameters are float or double +;;; +;;; It must move the parameters passed in registers to their stack location, +;;; call ffi_closure_SYSV for the actual work, then return the result. +;;; +ffi_closure_OUTER PROC FRAME + ;; save actual arguments to their stack space. + test r11, 1 + jne first_is_float + mov QWORD PTR [rsp+8], rcx + jmp second +first_is_float: + movlpd QWORD PTR [rsp+8], xmm0 + +second: + test r11, 2 + jne second_is_float + mov QWORD PTR [rsp+16], rdx + jmp third +second_is_float: + movlpd QWORD PTR [rsp+16], xmm1 + +third: + test r11, 4 + jne third_is_float + mov QWORD PTR [rsp+24], r8 + jmp forth +third_is_float: + movlpd QWORD PTR [rsp+24], xmm2 + +forth: + test r11, 8 + jne forth_is_float + mov QWORD PTR [rsp+32], r9 + jmp done +forth_is_float: + movlpd QWORD PTR [rsp+32], xmm3 + +done: +.ALLOCSTACK 40 + sub rsp, 40 +.ENDPROLOG + mov rcx, rax ; context is first parameter + mov rdx, rsp ; stack is second parameter + add rdx, 40 ; correct our own area + mov rax, ffi_closure_SYSV + call rax ; call the real closure function + ;; Here, code is missing that handles float return values + add rsp, 40 + movd xmm0, rax ; In case the closure returned a float. + ret 0 +ffi_closure_OUTER ENDP + + +;;; ffi_call_AMD64 + +stack$ = 0 +prepfunc$ = 32 +ecif$ = 40 +bytes$ = 48 +flags$ = 56 +rvalue$ = 64 +fn$ = 72 + +ffi_call_AMD64 PROC FRAME + + mov QWORD PTR [rsp+32], r9 + mov QWORD PTR [rsp+24], r8 + mov QWORD PTR [rsp+16], rdx + mov QWORD PTR [rsp+8], rcx +.PUSHREG rbp + push rbp +.ALLOCSTACK 48 + sub rsp, 48 ; 00000030H +.SETFRAME rbp, 32 + lea rbp, QWORD PTR [rsp+32] +.ENDPROLOG + + mov eax, DWORD PTR bytes$[rbp] + add rax, 15 + and rax, -16 + call __chkstk + sub rsp, rax + lea rax, QWORD PTR [rsp+32] + mov QWORD PTR stack$[rbp], rax + + mov rdx, QWORD PTR ecif$[rbp] + mov rcx, QWORD PTR stack$[rbp] + call QWORD PTR prepfunc$[rbp] + + mov rsp, QWORD PTR stack$[rbp] + + movlpd xmm3, QWORD PTR [rsp+24] + movd r9, xmm3 + + movlpd xmm2, QWORD PTR [rsp+16] + movd r8, xmm2 + + movlpd xmm1, QWORD PTR [rsp+8] + movd rdx, xmm1 + + movlpd xmm0, QWORD PTR [rsp] + movd rcx, xmm0 + + call QWORD PTR fn$[rbp] +ret_int$: + cmp DWORD PTR flags$[rbp], 1 ; FFI_TYPE_INT + jne ret_float$ + + mov rcx, QWORD PTR rvalue$[rbp] + mov DWORD PTR [rcx], eax + jmp SHORT ret_nothing$ + +ret_float$: + cmp DWORD PTR flags$[rbp], 2 ; FFI_TYPE_FLOAT + jne SHORT ret_double$ + + mov rax, QWORD PTR rvalue$[rbp] + movlpd QWORD PTR [rax], xmm0 + jmp SHORT ret_nothing$ + +ret_double$: + cmp DWORD PTR flags$[rbp], 3 ; FFI_TYPE_DOUBLE + jne SHORT ret_int64$ + + mov rax, QWORD PTR rvalue$[rbp] + movlpd QWORD PTR [rax], xmm0 + jmp SHORT ret_nothing$ + +ret_int64$: + cmp DWORD PTR flags$[rbp], 12 ; FFI_TYPE_SINT64 + jne ret_nothing$ + + mov rcx, QWORD PTR rvalue$[rbp] + mov QWORD PTR [rcx], rax + jmp SHORT ret_nothing$ + +ret_nothing$: + xor eax, eax + + lea rsp, QWORD PTR [rbp+16] + pop rbp + ret 0 +ffi_call_AMD64 ENDP +_TEXT ENDS +END diff --git a/c/libffi_msvc/win64.obj b/c/libffi_msvc/win64.obj new file mode 100644 index 0000000..38d3cd1 Binary files /dev/null and b/c/libffi_msvc/win64.obj differ diff --git a/c/malloc_closure.h b/c/malloc_closure.h new file mode 100644 index 0000000..bebb93d --- /dev/null +++ b/c/malloc_closure.h @@ -0,0 +1,176 @@ +/* + * This file is from CPython's Modules/_ctypes/malloc_closure.c + * and has received some edits. + */ + +#include +#ifdef MS_WIN32 +#include +#else +#include +#include +# if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +# endif +#endif + +/* On PaX enable kernels that have MPROTECT enable we can't use PROT_EXEC. + + This is, apparently, an undocumented change to ffi_prep_closure(): + depending on the Linux kernel we're running on, we must give it a + mmap that is either PROT_READ|PROT_WRITE|PROT_EXEC or only + PROT_READ|PROT_WRITE. In the latter case, just trying to obtain a + mmap with PROT_READ|PROT_WRITE|PROT_EXEC would kill our process(!), + but in that situation libffi is fine with only PROT_READ|PROT_WRITE. + There is nothing in the libffi API to know that, though, so we have + to guess by parsing /proc/self/status. "Meh." + */ +#ifdef __linux__ +#include + +static int emutramp_enabled = -1; + +static int +emutramp_enabled_check (void) +{ + char *buf = NULL; + size_t len = 0; + FILE *f; + int ret; + f = fopen ("/proc/self/status", "r"); + if (f == NULL) + return 0; + ret = 0; + + while (getline (&buf, &len, f) != -1) + if (!strncmp (buf, "PaX:", 4)) + { + char emutramp; + if (sscanf (buf, "%*s %*c%c", &emutramp) == 1) + ret = (emutramp == 'E'); + break; + } + free (buf); + fclose (f); + return ret; +} + +#define is_emutramp_enabled() (emutramp_enabled >= 0 ? emutramp_enabled \ + : (emutramp_enabled = emutramp_enabled_check ())) +#else +#define is_emutramp_enabled() 0 +#endif + + +/* 'allocate_num_pages' is dynamically adjusted starting from one + page. It grows by a factor of PAGE_ALLOCATION_GROWTH_RATE. This is + meant to handle both the common case of not needing a lot of pages, + and the rare case of needing many of them. Systems in general have a + limit of how many mmap'd blocks can be open. +*/ + +#define PAGE_ALLOCATION_GROWTH_RATE 1.3 + +static Py_ssize_t allocate_num_pages = 0; + +/* #define MALLOC_CLOSURE_DEBUG */ /* enable for some debugging output */ + +/******************************************************************/ + +union mmaped_block { + ffi_closure closure; + union mmaped_block *next; +}; + +static union mmaped_block *free_list = 0; +static Py_ssize_t _pagesize = 0; + +static void more_core(void) +{ + union mmaped_block *item; + Py_ssize_t count, i; + +/* determine the pagesize */ +#ifdef MS_WIN32 + if (!_pagesize) { + SYSTEM_INFO systeminfo; + GetSystemInfo(&systeminfo); + _pagesize = systeminfo.dwPageSize; + } +#else + if (!_pagesize) { +#ifdef _SC_PAGESIZE + _pagesize = sysconf(_SC_PAGESIZE); +#else + _pagesize = getpagesize(); +#endif + } +#endif + if (_pagesize <= 0) + _pagesize = 4096; + + /* bump 'allocate_num_pages' */ + allocate_num_pages = 1 + ( + (Py_ssize_t)(allocate_num_pages * PAGE_ALLOCATION_GROWTH_RATE)); + + /* calculate the number of mmaped_blocks to allocate */ + count = (allocate_num_pages * _pagesize) / sizeof(union mmaped_block); + + /* allocate a memory block */ +#ifdef MS_WIN32 + item = (union mmaped_block *)VirtualAlloc(NULL, + count * sizeof(union mmaped_block), + MEM_COMMIT, + PAGE_EXECUTE_READWRITE); + if (item == NULL) + return; +#else + { + int prot = PROT_READ | PROT_WRITE | PROT_EXEC; + if (is_emutramp_enabled ()) + prot &= ~PROT_EXEC; + item = (union mmaped_block *)mmap(NULL, + allocate_num_pages * _pagesize, + prot, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + if (item == (void *)MAP_FAILED) + return; + } +#endif + +#ifdef MALLOC_CLOSURE_DEBUG + printf("block at %p allocated (%ld bytes), %ld mmaped_blocks\n", + item, (long)(allocate_num_pages * _pagesize), (long)count); +#endif + /* put them into the free list */ + for (i = 0; i < count; ++i) { + item->next = free_list; + free_list = item; + ++item; + } +} + +/******************************************************************/ + +/* put the item back into the free list */ +static void cffi_closure_free(ffi_closure *p) +{ + union mmaped_block *item = (union mmaped_block *)p; + item->next = free_list; + free_list = item; +} + +/* return one item from the free list, allocating more if needed */ +static ffi_closure *cffi_closure_alloc(void) +{ + union mmaped_block *item; + if (!free_list) + more_core(); + if (!free_list) + return NULL; + item = free_list; + free_list = item->next; + return &item->closure; +} diff --git a/c/minibuffer.h b/c/minibuffer.h new file mode 100644 index 0000000..f3f5ca1 --- /dev/null +++ b/c/minibuffer.h @@ -0,0 +1,408 @@ + +/* Implementation of a C object with the 'buffer' or 'memoryview' + * interface at C-level (as approriate for the version of Python we're + * compiling for), but only a minimal but *consistent* part of the + * 'buffer' interface at application level. + */ + +typedef struct { + PyObject_HEAD + char *mb_data; + Py_ssize_t mb_size; + PyObject *mb_keepalive; + PyObject *mb_weakreflist; /* weakref support */ +} MiniBufferObj; + +static Py_ssize_t mb_length(MiniBufferObj *self) +{ + return self->mb_size; +} + +static PyObject *mb_item(MiniBufferObj *self, Py_ssize_t idx) +{ + if (idx < 0 || idx >= self->mb_size ) { + PyErr_SetString(PyExc_IndexError, "buffer index out of range"); + return NULL; + } + return PyBytes_FromStringAndSize(self->mb_data + idx, 1); +} + +static PyObject *mb_slice(MiniBufferObj *self, + Py_ssize_t left, Py_ssize_t right) +{ + Py_ssize_t size = self->mb_size; + if (left < 0) left = 0; + if (right > size) right = size; + if (left > right) left = right; + return PyBytes_FromStringAndSize(self->mb_data + left, right - left); +} + +static int mb_ass_item(MiniBufferObj *self, Py_ssize_t idx, PyObject *other) +{ + if (idx < 0 || idx >= self->mb_size) { + PyErr_SetString(PyExc_IndexError, + "buffer assignment index out of range"); + return -1; + } + if (PyBytes_Check(other) && PyBytes_GET_SIZE(other) == 1) { + self->mb_data[idx] = PyBytes_AS_STRING(other)[0]; + return 0; + } + else { + PyErr_Format(PyExc_TypeError, + "must assign a "STR_OR_BYTES + " of length 1, not %.200s", Py_TYPE(other)->tp_name); + return -1; + } +} + +/* forward: from _cffi_backend.c */ +static int _fetch_as_buffer(PyObject *x, Py_buffer *view, int writable_only); + +static int mb_ass_slice(MiniBufferObj *self, + Py_ssize_t left, Py_ssize_t right, PyObject *other) +{ + Py_ssize_t count; + Py_ssize_t size = self->mb_size; + Py_buffer src_view; + + if (_fetch_as_buffer(other, &src_view, 0) < 0) + return -1; + + if (left < 0) left = 0; + if (right > size) right = size; + if (left > right) left = right; + + count = right - left; + if (count != src_view.len) { + PyBuffer_Release(&src_view); + PyErr_SetString(PyExc_ValueError, + "right operand length must match slice length"); + return -1; + } + memcpy(self->mb_data + left, src_view.buf, count); + PyBuffer_Release(&src_view); + return 0; +} + +#if PY_MAJOR_VERSION < 3 +static Py_ssize_t mb_getdata(MiniBufferObj *self, Py_ssize_t idx, void **pp) +{ + *pp = self->mb_data; + return self->mb_size; +} + +static Py_ssize_t mb_getsegcount(MiniBufferObj *self, Py_ssize_t *lenp) +{ + if (lenp) + *lenp = self->mb_size; + return 1; +} + +static PyObject *mb_str(MiniBufferObj *self) +{ + /* Python 2: we want str(buffer) to behave like buffer[:], because + that's what bytes(buffer) does on Python 3 and there is no way + we can prevent this. */ + return PyString_FromStringAndSize(self->mb_data, self->mb_size); +} +#endif + +static int mb_getbuf(MiniBufferObj *self, Py_buffer *view, int flags) +{ + return PyBuffer_FillInfo(view, (PyObject *)self, + self->mb_data, self->mb_size, + /*readonly=*/0, flags); +} + +static PySequenceMethods mb_as_sequence = { + (lenfunc)mb_length, /*sq_length*/ + (binaryfunc)0, /*sq_concat*/ + (ssizeargfunc)0, /*sq_repeat*/ + (ssizeargfunc)mb_item, /*sq_item*/ + (ssizessizeargfunc)mb_slice, /*sq_slice*/ + (ssizeobjargproc)mb_ass_item, /*sq_ass_item*/ + (ssizessizeobjargproc)mb_ass_slice, /*sq_ass_slice*/ +}; + +static PyBufferProcs mb_as_buffer = { +#if PY_MAJOR_VERSION < 3 + (readbufferproc)mb_getdata, + (writebufferproc)mb_getdata, + (segcountproc)mb_getsegcount, + (charbufferproc)mb_getdata, +#endif + (getbufferproc)mb_getbuf, + (releasebufferproc)0, +}; + +static void +mb_dealloc(MiniBufferObj *ob) +{ + PyObject_GC_UnTrack(ob); + if (ob->mb_weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *)ob); + Py_XDECREF(ob->mb_keepalive); + Py_TYPE(ob)->tp_free((PyObject *)ob); +} + +static int +mb_traverse(MiniBufferObj *ob, visitproc visit, void *arg) +{ + Py_VISIT(ob->mb_keepalive); + return 0; +} + +static int +mb_clear(MiniBufferObj *ob) +{ + Py_CLEAR(ob->mb_keepalive); + return 0; +} + +static PyObject * +mb_richcompare(PyObject *self, PyObject *other, int op) +{ + Py_ssize_t self_size, other_size; + Py_buffer self_bytes, other_bytes; + PyObject *res; + Py_ssize_t minsize; + int cmp, rc; + + /* Bytes can be compared to anything that supports the (binary) + buffer API. Except that a comparison with Unicode is always an + error, even if the comparison is for equality. */ + rc = PyObject_IsInstance(self, (PyObject*)&PyUnicode_Type); + if (!rc) + rc = PyObject_IsInstance(other, (PyObject*)&PyUnicode_Type); + if (rc < 0) + return NULL; + if (rc) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + if (PyObject_GetBuffer(self, &self_bytes, PyBUF_SIMPLE) != 0) { + PyErr_Clear(); + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + + } + self_size = self_bytes.len; + + if (PyObject_GetBuffer(other, &other_bytes, PyBUF_SIMPLE) != 0) { + PyErr_Clear(); + PyBuffer_Release(&self_bytes); + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + + } + other_size = other_bytes.len; + + if (self_size != other_size && (op == Py_EQ || op == Py_NE)) { + /* Shortcut: if the lengths differ, the objects differ */ + cmp = (op == Py_NE); + } + else { + minsize = self_size; + if (other_size < minsize) + minsize = other_size; + + cmp = memcmp(self_bytes.buf, other_bytes.buf, minsize); + /* In ISO C, memcmp() guarantees to use unsigned bytes! */ + + if (cmp == 0) { + if (self_size < other_size) + cmp = -1; + else if (self_size > other_size) + cmp = 1; + } + + switch (op) { + case Py_LT: cmp = cmp < 0; break; + case Py_LE: cmp = cmp <= 0; break; + case Py_EQ: cmp = cmp == 0; break; + case Py_NE: cmp = cmp != 0; break; + case Py_GT: cmp = cmp > 0; break; + case Py_GE: cmp = cmp >= 0; break; + } + } + + res = cmp ? Py_True : Py_False; + PyBuffer_Release(&self_bytes); + PyBuffer_Release(&other_bytes); + Py_INCREF(res); + return res; +} + +#if PY_MAJOR_VERSION >= 3 +/* pfffffffffffff pages of copy-paste from listobject.c */ + +/* pfffffffffffff#2: the PySlice_GetIndicesEx() *macro* should not + be called, because C extension modules compiled with it differ + on ABI between 3.6.0, 3.6.1 and 3.6.2. */ +#if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && !defined(PYPY_VERSION) +#undef PySlice_GetIndicesEx +#endif + +static PyObject *mb_subscript(MiniBufferObj *self, PyObject *item) +{ + if (PyIndex_Check(item)) { + Py_ssize_t i; + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return NULL; + if (i < 0) + i += self->mb_size; + return mb_item(self, i); + } + else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step, slicelength; + + if (PySlice_GetIndicesEx(item, self->mb_size, + &start, &stop, &step, &slicelength) < 0) + return NULL; + + if (step == 1) + return mb_slice(self, start, stop); + else { + PyErr_SetString(PyExc_TypeError, + "buffer doesn't support slicing with step != 1"); + return NULL; + } + } + else { + PyErr_Format(PyExc_TypeError, + "buffer indices must be integers, not %.200s", + item->ob_type->tp_name); + return NULL; + } +} +static int +mb_ass_subscript(MiniBufferObj* self, PyObject* item, PyObject* value) +{ + if (PyIndex_Check(item)) { + Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return -1; + if (i < 0) + i += self->mb_size; + return mb_ass_item(self, i, value); + } + else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step, slicelength; + + if (PySlice_GetIndicesEx(item, self->mb_size, + &start, &stop, &step, &slicelength) < 0) { + return -1; + } + + if (step == 1) + return mb_ass_slice(self, start, stop, value); + else { + PyErr_SetString(PyExc_TypeError, + "buffer doesn't support slicing with step != 1"); + return -1; + } + } + else { + PyErr_Format(PyExc_TypeError, + "buffer indices must be integers, not %.200s", + item->ob_type->tp_name); + return -1; + } +} + +static PyMappingMethods mb_as_mapping = { + (lenfunc)mb_length, /*mp_length*/ + (binaryfunc)mb_subscript, /*mp_subscript*/ + (objobjargproc)mb_ass_subscript, /*mp_ass_subscript*/ +}; +#endif + +#if PY_MAJOR_VERSION >= 3 +# define MINIBUF_TPFLAGS 0 +#else +# define MINIBUF_TPFLAGS (Py_TPFLAGS_HAVE_GETCHARBUFFER | Py_TPFLAGS_HAVE_NEWBUFFER) +#endif + +PyDoc_STRVAR(ffi_buffer_doc, +"ffi.buffer(cdata[, byte_size]):\n" +"Return a read-write buffer object that references the raw C data\n" +"pointed to by the given 'cdata'. The 'cdata' must be a pointer or an\n" +"array. Can be passed to functions expecting a buffer, or directly\n" +"manipulated with:\n" +"\n" +" buf[:] get a copy of it in a regular string, or\n" +" buf[idx] as a single character\n" +" buf[:] = ...\n" +" buf[idx] = ... change the content"); + +static PyObject * /* forward, implemented in _cffi_backend.c */ +b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds); + + +static PyTypeObject MiniBuffer_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.buffer", + sizeof(MiniBufferObj), + 0, + (destructor)mb_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &mb_as_sequence, /* tp_as_sequence */ +#if PY_MAJOR_VERSION < 3 + 0, /* tp_as_mapping */ +#else + &mb_as_mapping, /* tp_as_mapping */ +#endif + 0, /* tp_hash */ + 0, /* tp_call */ +#if PY_MAJOR_VERSION < 3 + (reprfunc)mb_str, /* tp_str */ +#else + 0, /* tp_str */ +#endif + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + &mb_as_buffer, /* tp_as_buffer */ + (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + MINIBUF_TPFLAGS), /* tp_flags */ + ffi_buffer_doc, /* tp_doc */ + (traverseproc)mb_traverse, /* tp_traverse */ + (inquiry)mb_clear, /* tp_clear */ + (richcmpfunc)mb_richcompare, /* tp_richcompare */ + offsetof(MiniBufferObj, mb_weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_buffer_new, /* tp_new */ + 0, /* tp_free */ +}; + +static PyObject *minibuffer_new(char *data, Py_ssize_t size, + PyObject *keepalive) +{ + MiniBufferObj *ob = PyObject_GC_New(MiniBufferObj, &MiniBuffer_Type); + if (ob != NULL) { + ob->mb_data = data; + ob->mb_size = size; + ob->mb_keepalive = keepalive; Py_INCREF(keepalive); + ob->mb_weakreflist = NULL; + PyObject_GC_Track(ob); + } + return (PyObject *)ob; +} diff --git a/c/misc_thread_common.h b/c/misc_thread_common.h new file mode 100644 index 0000000..66e2835 --- /dev/null +++ b/c/misc_thread_common.h @@ -0,0 +1,371 @@ +#ifndef WITH_THREAD +# error "xxx no-thread configuration not tested, please report if you need that" +#endif +#include "pythread.h" + + +struct cffi_tls_s { + /* The current thread's ThreadCanaryObj. This is only non-null in + case cffi builds the thread state here. It remains null if this + thread had already a thread state provided by CPython. */ + struct thread_canary_s *local_thread_canary; + +#ifndef USE__THREAD + /* The saved errno. If the C compiler supports '__thread', then + we use that instead. */ + int saved_errno; +#endif + +#ifdef MS_WIN32 + /* The saved lasterror, on Windows. */ + int saved_lasterror; +#endif +}; + +static struct cffi_tls_s *get_cffi_tls(void); /* in misc_thread_posix.h + or misc_win32.h */ + + +/* We try to keep the PyThreadState around in a thread not started by + * Python but where cffi callbacks occur. If we didn't do that, then + * the standard logic in PyGILState_Ensure() and PyGILState_Release() + * would create a new PyThreadState and completely free it for every + * single call. For some applications, this is a huge slow-down. + * + * As shown by issue #362, it is quite messy to do. The current + * solution is to keep the PyThreadState alive by incrementing its + * 'gilstate_counter'. We detect thread shut-down, and we put the + * PyThreadState inside a list of zombies (we can't free it + * immediately because we don't have the GIL at that point in time). + * We also detect other pieces of code (notably Py_Finalize()) which + * clear and free PyThreadStates under our feet, using ThreadCanaryObj. + */ + +#define TLS_ZOM_LOCK() PyThread_acquire_lock(cffi_zombie_lock, WAIT_LOCK) +#define TLS_ZOM_UNLOCK() PyThread_release_lock(cffi_zombie_lock) +static PyThread_type_lock cffi_zombie_lock = NULL; + + +/* A 'canary' object is created in a thread when there is a callback + invoked, and that thread has no PyThreadState so far. It is an + object of reference count equal to 1, which is stored in the + PyThreadState->dict. Two things can occur then: + + 1. The PyThreadState can be forcefully cleared by Py_Finalize(). + Then thread_canary_dealloc() is called, and we have to cancel + the hacks we did to keep the PyThreadState alive. + + 2. The thread finishes. In that case, we put the canary in a list + of zombies, and at some convenient time later when we have the + GIL, we free all PyThreadStates in the zombie list. + + Some more fun comes from the fact that thread_canary_dealloc() can + be called at a point where the canary is in the zombie list already. + Also, the various pieces are freed at specific points in time, and + we must make sure not to access already-freed structures: + + - the struct cffi_tls_s is valid until the thread shuts down, and + then it is freed by cffi_thread_shutdown(). + + - the canary is a normal Python object, but we have a borrowed + reference to it from cffi_tls_s.local_thread_canary. + */ + +typedef struct thread_canary_s { + PyObject_HEAD + struct thread_canary_s *zombie_prev, *zombie_next; + PyThreadState *tstate; + struct cffi_tls_s *tls; +} ThreadCanaryObj; + +static PyTypeObject ThreadCanary_Type; /* forward */ +static ThreadCanaryObj cffi_zombie_head; + +static void +_thread_canary_detach_with_lock(ThreadCanaryObj *ob) +{ + /* must be called with both the GIL and TLS_ZOM_LOCK. */ + ThreadCanaryObj *p, *n; + p = ob->zombie_prev; + n = ob->zombie_next; + p->zombie_next = n; + n->zombie_prev = p; + ob->zombie_prev = NULL; + ob->zombie_next = NULL; +} + +static void +thread_canary_dealloc(ThreadCanaryObj *ob) +{ + /* this ThreadCanaryObj is being freed: if it is in the zombie + chained list, remove it. Thread-safety: 'zombie_next' amd + 'local_thread_canary' accesses need to be protected with + the TLS_ZOM_LOCK. + */ + TLS_ZOM_LOCK(); + if (ob->zombie_next != NULL) { + //fprintf(stderr, "thread_canary_dealloc(%p): ZOMBIE\n", ob); + _thread_canary_detach_with_lock(ob); + } + else { + //fprintf(stderr, "thread_canary_dealloc(%p): not a zombie\n", ob); + } + + if (ob->tls != NULL) { + //fprintf(stderr, "thread_canary_dealloc(%p): was local_thread_canary\n", ob); + assert(ob->tls->local_thread_canary == ob); + ob->tls->local_thread_canary = NULL; + } + TLS_ZOM_UNLOCK(); + + PyObject_Del((PyObject *)ob); +} + +static void +thread_canary_make_zombie(ThreadCanaryObj *ob) +{ + /* This must be called without the GIL, but with the TLS_ZOM_LOCK. + It must be called at most once for a given ThreadCanaryObj. */ + ThreadCanaryObj *last; + + //fprintf(stderr, "thread_canary_make_zombie(%p)\n", ob); + if (ob->zombie_next) + Py_FatalError("cffi: ThreadCanaryObj is already a zombie"); + last = cffi_zombie_head.zombie_prev; + ob->zombie_next = &cffi_zombie_head; + ob->zombie_prev = last; + last->zombie_next = ob; + cffi_zombie_head.zombie_prev = ob; +} + +static void +thread_canary_free_zombies(void) +{ + /* This must be called with the GIL. */ + if (cffi_zombie_head.zombie_next == &cffi_zombie_head) + return; /* fast path */ + + while (1) { + ThreadCanaryObj *ob; + PyThreadState *tstate = NULL; + + TLS_ZOM_LOCK(); + ob = cffi_zombie_head.zombie_next; + if (ob != &cffi_zombie_head) { + tstate = ob->tstate; + //fprintf(stderr, "thread_canary_free_zombie(%p) tstate=%p\n", ob, tstate); + _thread_canary_detach_with_lock(ob); + if (tstate == NULL) + Py_FatalError("cffi: invalid ThreadCanaryObj->tstate"); + } + TLS_ZOM_UNLOCK(); + + if (tstate == NULL) + break; + PyThreadState_Clear(tstate); /* calls thread_canary_dealloc on 'ob', + but now ob->zombie_next == NULL. */ + PyThreadState_Delete(tstate); + //fprintf(stderr, "thread_canary_free_zombie: cleared and deleted tstate=%p\n", tstate); + } + //fprintf(stderr, "thread_canary_free_zombie: end\n"); +} + +static void +thread_canary_register(PyThreadState *tstate) +{ + /* called with the GIL; 'tstate' is the current PyThreadState. */ + ThreadCanaryObj *canary; + PyObject *tdict; + struct cffi_tls_s *tls; + int err; + + /* first free the zombies, if any */ + thread_canary_free_zombies(); + + tls = get_cffi_tls(); + if (tls == NULL) + goto ignore_error; + + tdict = PyThreadState_GetDict(); + if (tdict == NULL) + goto ignore_error; + + canary = PyObject_New(ThreadCanaryObj, &ThreadCanary_Type); + //fprintf(stderr, "thread_canary_register(%p): tstate=%p tls=%p\n", canary, tstate, tls); + if (canary == NULL) + goto ignore_error; + canary->zombie_prev = NULL; + canary->zombie_next = NULL; + canary->tstate = tstate; + canary->tls = tls; + + err = PyDict_SetItemString(tdict, "cffi.thread.canary", (PyObject *)canary); + Py_DECREF(canary); + if (err < 0) + goto ignore_error; + + /* thread-safety: we have the GIL here, and 'tstate' is the one that + corresponds to our own thread. We are allocating a new 'canary' + and setting it up for our own thread, both in 'tdict' (which owns + the reference) and in 'tls->local_thread_canary' (which doesn't). */ + assert(Py_REFCNT(canary) == 1); + tls->local_thread_canary = canary; + tstate->gilstate_counter++; + /* ^^^ this means 'tstate' will never be automatically freed by + PyGILState_Release() */ + return; + + ignore_error: + PyErr_Clear(); +} + +static PyTypeObject ThreadCanary_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_cffi_backend.thread_canary", + sizeof(ThreadCanaryObj), + 0, + (destructor)thread_canary_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ +}; + +static void init_cffi_tls_zombie(void) +{ + cffi_zombie_head.zombie_next = &cffi_zombie_head; + cffi_zombie_head.zombie_prev = &cffi_zombie_head; + cffi_zombie_lock = PyThread_allocate_lock(); + if (cffi_zombie_lock == NULL) + PyErr_SetString(PyExc_SystemError, "can't allocate cffi_zombie_lock"); +} + +static void cffi_thread_shutdown(void *p) +{ + /* this function is called from misc_thread_posix or misc_win32 + when a thread is about to end. */ + struct cffi_tls_s *tls = (struct cffi_tls_s *)p; + + /* thread-safety: this field 'local_thread_canary' can be reset + to NULL in parallel, protected by TLS_ZOM_LOCK. */ + TLS_ZOM_LOCK(); + if (tls->local_thread_canary != NULL) { + tls->local_thread_canary->tls = NULL; + thread_canary_make_zombie(tls->local_thread_canary); + } + TLS_ZOM_UNLOCK(); + //fprintf(stderr, "thread_shutdown(%p)\n", tls); + free(tls); +} + +/* USE__THREAD is defined by setup.py if it finds that it is + syntactically valid to use "__thread" with this C compiler. */ +#ifdef USE__THREAD + +static __thread int cffi_saved_errno = 0; +static void save_errno_only(void) { cffi_saved_errno = errno; } +static void restore_errno_only(void) { errno = cffi_saved_errno; } + +#else + +static void save_errno_only(void) +{ + int saved = errno; + struct cffi_tls_s *tls = get_cffi_tls(); + if (tls != NULL) + tls->saved_errno = saved; +} + +static void restore_errno_only(void) +{ + struct cffi_tls_s *tls = get_cffi_tls(); + if (tls != NULL) + errno = tls->saved_errno; +} + +#endif + + +/* MESS. We can't use PyThreadState_GET(), because that calls + PyThreadState_Get() which fails an assert if the result is NULL. + + * in Python 2.7 and <= 3.4, the variable _PyThreadState_Current + is directly available, so use that. + + * in Python 3.5, the variable is available too, but it might be + the case that the headers don't define it (this changed in 3.5.1). + In case we're compiling with 3.5.x with x >= 1, we need to + manually define this variable. + + * in Python >= 3.6 there is _PyThreadState_UncheckedGet(). + It was added in 3.5.2 but should never be used in 3.5.x + because it is not available in 3.5.0 or 3.5.1. +*/ +#if PY_VERSION_HEX >= 0x03050100 && PY_VERSION_HEX < 0x03060000 +PyAPI_DATA(void *volatile) _PyThreadState_Current; +#endif + +static PyThreadState *get_current_ts(void) +{ +#if PY_VERSION_HEX >= 0x03060000 + return _PyThreadState_UncheckedGet(); +#elif defined(_Py_atomic_load_relaxed) + return (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current); +#else + return (PyThreadState*)_PyThreadState_Current; /* assume atomic read */ +#endif +} + +static PyGILState_STATE gil_ensure(void) +{ + /* Called at the start of a callback. Replacement for + PyGILState_Ensure(). + */ + PyGILState_STATE result; + PyThreadState *ts = PyGILState_GetThisThreadState(); + + if (ts != NULL) { + ts->gilstate_counter++; + if (ts != get_current_ts()) { + /* common case: 'ts' is our non-current thread state and + we have to make it current and acquire the GIL */ + PyEval_RestoreThread(ts); + return PyGILState_UNLOCKED; + } + else { + return PyGILState_LOCKED; + } + } + else { + /* no thread state here so far. */ + result = PyGILState_Ensure(); + assert(result == PyGILState_UNLOCKED); + + ts = PyGILState_GetThisThreadState(); + assert(ts != NULL); + assert(ts == get_current_ts()); + assert(ts->gilstate_counter >= 1); + + /* Use the ThreadCanary mechanism to keep 'ts' alive until the + thread really shuts down */ + thread_canary_register(ts); + + return result; + } +} + +static void gil_release(PyGILState_STATE oldstate) +{ + PyGILState_Release(oldstate); +} diff --git a/c/misc_thread_posix.h b/c/misc_thread_posix.h new file mode 100644 index 0000000..bcc0177 --- /dev/null +++ b/c/misc_thread_posix.h @@ -0,0 +1,49 @@ +/* + Logic for a better replacement of PyGILState_Ensure(). + + This version is ready to handle the case of a non-Python-started + thread in which we do a large number of calls to CFFI callbacks. If + we were to rely on PyGILState_Ensure() for that, we would constantly + be creating and destroying PyThreadStates---it is slow, and + PyThreadState_Delete() will actually walk the list of all thread + states, making it O(n). :-( + + This version only creates one PyThreadState object the first time we + see a given thread, and keep it alive until the thread is really + shut down, using a destructor on the tls key. +*/ + +#include +#include "misc_thread_common.h" + + +static pthread_key_t cffi_tls_key; + +static void init_cffi_tls(void) +{ + if (pthread_key_create(&cffi_tls_key, &cffi_thread_shutdown) != 0) + PyErr_SetString(PyExc_OSError, "pthread_key_create() failed"); +} + +static struct cffi_tls_s *_make_cffi_tls(void) +{ + void *p = calloc(1, sizeof(struct cffi_tls_s)); + if (p == NULL) + return NULL; + if (pthread_setspecific(cffi_tls_key, p) != 0) { + free(p); + return NULL; + } + return p; +} + +static struct cffi_tls_s *get_cffi_tls(void) +{ + void *p = pthread_getspecific(cffi_tls_key); + if (p == NULL) + p = _make_cffi_tls(); + return (struct cffi_tls_s *)p; +} + +#define save_errno save_errno_only +#define restore_errno restore_errno_only diff --git a/c/misc_win32.h b/c/misc_win32.h new file mode 100644 index 0000000..07b76c1 --- /dev/null +++ b/c/misc_win32.h @@ -0,0 +1,241 @@ +#include /* for alloca() */ + + +/************************************************************/ +/* errno and GetLastError support */ + +#include "misc_thread_common.h" + +static DWORD cffi_tls_index = TLS_OUT_OF_INDEXES; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, + DWORD reason_for_call, + LPVOID reserved) +{ + LPVOID p; + + switch (reason_for_call) { + + case DLL_THREAD_DETACH: + if (cffi_tls_index != TLS_OUT_OF_INDEXES) { + p = TlsGetValue(cffi_tls_index); + if (p != NULL) { + TlsSetValue(cffi_tls_index, NULL); + cffi_thread_shutdown(p); + } + } + break; + + default: + break; + } + return TRUE; +} + +static void init_cffi_tls(void) +{ + if (cffi_tls_index == TLS_OUT_OF_INDEXES) { + cffi_tls_index = TlsAlloc(); + if (cffi_tls_index == TLS_OUT_OF_INDEXES) + PyErr_SetString(PyExc_WindowsError, "TlsAlloc() failed"); + } +} + +static struct cffi_tls_s *get_cffi_tls(void) +{ + LPVOID p = TlsGetValue(cffi_tls_index); + + if (p == NULL) { + p = malloc(sizeof(struct cffi_tls_s)); + if (p == NULL) + return NULL; + memset(p, 0, sizeof(struct cffi_tls_s)); + TlsSetValue(cffi_tls_index, p); + } + return (struct cffi_tls_s *)p; +} + +#ifdef USE__THREAD +# error "unexpected USE__THREAD on Windows" +#endif + +static void save_errno(void) +{ + int current_err = errno; + int current_lasterr = GetLastError(); + struct cffi_tls_s *p = get_cffi_tls(); + if (p != NULL) { + p->saved_errno = current_err; + p->saved_lasterror = current_lasterr; + } + /* else: cannot report the error */ +} + +static void restore_errno(void) +{ + struct cffi_tls_s *p = get_cffi_tls(); + if (p != NULL) { + SetLastError(p->saved_lasterror); + errno = p->saved_errno; + } + /* else: cannot report the error */ +} + +/************************************************************/ + + +#if PY_MAJOR_VERSION >= 3 +static PyObject *b_getwinerror(PyObject *self, PyObject *args, PyObject *kwds) +{ + int err = -1; + int len; + WCHAR *s_buf = NULL; /* Free via LocalFree */ + PyObject *v, *message; + static char *keywords[] = {"code", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", keywords, &err)) + return NULL; + + if (err == -1) { + struct cffi_tls_s *p = get_cffi_tls(); + if (p == NULL) + return PyErr_NoMemory(); + err = p->saved_lasterror; + } + + len = FormatMessageW( + /* Error API error */ + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, /* no message source */ + err, + MAKELANGID(LANG_NEUTRAL, + SUBLANG_DEFAULT), /* Default language */ + (LPWSTR) &s_buf, + 0, /* size not used */ + NULL); /* no args */ + if (len==0) { + /* Only seen this in out of mem situations */ + message = PyUnicode_FromFormat("Windows Error 0x%X", err); + } else { + /* remove trailing cr/lf and dots */ + while (len > 0 && (s_buf[len-1] <= L' ' || s_buf[len-1] == L'.')) + s_buf[--len] = L'\0'; + message = PyUnicode_FromWideChar(s_buf, len); + } + if (message != NULL) + v = Py_BuildValue("(iO)", err, message); + else + v = NULL; + LocalFree(s_buf); + return v; +} +#else +static PyObject *b_getwinerror(PyObject *self, PyObject *args, PyObject *kwds) +{ + int err = -1; + int len; + char *s; + char *s_buf = NULL; /* Free via LocalFree */ + char s_small_buf[40]; /* Room for "Windows Error 0xFFFFFFFFFFFFFFFF" */ + PyObject *v; + static char *keywords[] = {"code", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", keywords, &err)) + return NULL; + + if (err == -1) { + struct cffi_tls_s *p = get_cffi_tls(); + if (p == NULL) + return PyErr_NoMemory(); + err = p->saved_lasterror; + } + + len = FormatMessage( + /* Error API error */ + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, /* no message source */ + err, + MAKELANGID(LANG_NEUTRAL, + SUBLANG_DEFAULT), /* Default language */ + (LPTSTR) &s_buf, + 0, /* size not used */ + NULL); /* no args */ + if (len==0) { + /* Only seen this in out of mem situations */ + sprintf(s_small_buf, "Windows Error 0x%X", err); + s = s_small_buf; + s_buf = NULL; + } else { + s = s_buf; + /* remove trailing cr/lf and dots */ + while (len > 0 && (s[len-1] <= ' ' || s[len-1] == '.')) + s[--len] = '\0'; + } + v = Py_BuildValue("(is)", err, s); + LocalFree(s_buf); + return v; +} +#endif + + +/************************************************************/ +/* Emulate dlopen()&co. from the Windows API */ + +#define RTLD_LAZY 0 +#define RTLD_NOW 0 +#define RTLD_GLOBAL 0 +#define RTLD_LOCAL 0 + +static void *dlopen(const char *filename, int flag) +{ + return (void *)LoadLibraryA(filename); +} + +static void *dlopenW(const wchar_t *filename) +{ + return (void *)LoadLibraryW(filename); +} + +static void *dlsym(void *handle, const char *symbol) +{ + void *address = GetProcAddress((HMODULE)handle, symbol); +#ifndef MS_WIN64 + if (!address) { + /* If 'symbol' is not found, then try '_symbol@N' for N in + (0, 4, 8, 12, ..., 124). Unlike ctypes, we try to do that + for any symbol, although in theory it should only be done + for __stdcall functions. + */ + int i; + char *mangled_name = alloca(1 + strlen(symbol) + 1 + 3 + 1); + if (!mangled_name) + return NULL; + for (i = 0; i < 32; i++) { + sprintf(mangled_name, "_%s@%d", symbol, i * 4); + address = GetProcAddress((HMODULE)handle, mangled_name); + if (address) + break; + } + } +#endif + return address; +} + +static int dlclose(void *handle) +{ + return FreeLibrary((HMODULE)handle) ? 0 : -1; +} + +static const char *dlerror(void) +{ + static char buf[32]; + DWORD dw = GetLastError(); + if (dw == 0) + return NULL; + sprintf(buf, "error 0x%x", (unsigned int)dw); + return buf; +} diff --git a/c/parse_c_type.c b/c/parse_c_type.c new file mode 100644 index 0000000..698ef64 --- /dev/null +++ b/c/parse_c_type.c @@ -0,0 +1,847 @@ +#include +#include +#include +#include + +#define _CFFI_INTERNAL +#include "../cffi/parse_c_type.h" + + +enum token_e { + TOK_STAR='*', + TOK_OPEN_PAREN='(', + TOK_CLOSE_PAREN=')', + TOK_OPEN_BRACKET='[', + TOK_CLOSE_BRACKET=']', + TOK_COMMA=',', + + TOK_START=256, + TOK_END, + TOK_ERROR, + TOK_IDENTIFIER, + TOK_INTEGER, + TOK_DOTDOTDOT, + + /* keywords */ + TOK__BOOL, + TOK_CHAR, + TOK__COMPLEX, + TOK_CONST, + TOK_DOUBLE, + TOK_ENUM, + TOK_FLOAT, + //TOK__IMAGINARY, + TOK_INT, + TOK_LONG, + TOK_SHORT, + TOK_SIGNED, + TOK_STRUCT, + TOK_UNION, + TOK_UNSIGNED, + TOK_VOID, + TOK_VOLATILE, + + TOK_CDECL, + TOK_STDCALL, +}; + +typedef struct { + struct _cffi_parse_info_s *info; + const char *input, *p; + size_t size; // the next token is at 'p' and of length 'size' + enum token_e kind; + _cffi_opcode_t *output; + size_t output_index; +} token_t; + +static int is_space(char x) +{ + return (x == ' ' || x == '\f' || x == '\n' || x == '\r' || + x == '\t' || x == '\v'); +} + +static int is_ident_first(char x) +{ + return (('A' <= x && x <= 'Z') || ('a' <= x && x <= 'z') || x == '_' || + x == '$'); /* '$' in names is supported here, for the struct + names invented by cparser */ +} + +static int is_digit(char x) +{ + return ('0' <= x && x <= '9'); +} + +static int is_hex_digit(char x) +{ + return (('0' <= x && x <= '9') || + ('A' <= x && x <= 'F') || + ('a' <= x && x <= 'f')); +} + +static int is_ident_next(char x) +{ + return (is_ident_first(x) || is_digit(x)); +} + +static char get_following_char(token_t *tok) +{ + const char *p = tok->p + tok->size; + if (tok->kind == TOK_ERROR) + return 0; + while (is_space(*p)) + p++; + return *p; +} + +static int number_of_commas(token_t *tok) +{ + const char *p = tok->p; + int result = 0; + int nesting = 0; + while (1) { + switch (*p++) { + case ',': result += !nesting; break; + case '(': nesting++; break; + case ')': if ((--nesting) < 0) return result; break; + case 0: return result; + default: break; + } + } +} + +static void next_token(token_t *tok) +{ + const char *p = tok->p + tok->size; + if (tok->kind == TOK_ERROR) + return; + while (!is_ident_first(*p)) { + if (is_space(*p)) { + p++; + } + else if (is_digit(*p)) { + tok->kind = TOK_INTEGER; + tok->p = p; + tok->size = 1; + if (p[1] == 'x' || p[1] == 'X') + tok->size = 2; + while (is_hex_digit(p[tok->size])) + tok->size++; + return; + } + else if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + tok->kind = TOK_DOTDOTDOT; + tok->p = p; + tok->size = 3; + return; + } + else if (*p) { + tok->kind = *p; + tok->p = p; + tok->size = 1; + return; + } + else { + tok->kind = TOK_END; + tok->p = p; + tok->size = 0; + return; + } + } + tok->kind = TOK_IDENTIFIER; + tok->p = p; + tok->size = 1; + while (is_ident_next(p[tok->size])) + tok->size++; + + switch (*p) { + case '_': + if (tok->size == 5 && !memcmp(p, "_Bool", 5)) tok->kind = TOK__BOOL; + if (tok->size == 7 && !memcmp(p,"__cdecl",7)) tok->kind = TOK_CDECL; + if (tok->size == 9 && !memcmp(p,"__stdcall",9))tok->kind = TOK_STDCALL; + if (tok->size == 8 && !memcmp(p,"_Complex",8)) tok->kind = TOK__COMPLEX; + break; + case 'c': + if (tok->size == 4 && !memcmp(p, "char", 4)) tok->kind = TOK_CHAR; + if (tok->size == 5 && !memcmp(p, "const", 5)) tok->kind = TOK_CONST; + break; + case 'd': + if (tok->size == 6 && !memcmp(p, "double", 6)) tok->kind = TOK_DOUBLE; + break; + case 'e': + if (tok->size == 4 && !memcmp(p, "enum", 4)) tok->kind = TOK_ENUM; + break; + case 'f': + if (tok->size == 5 && !memcmp(p, "float", 5)) tok->kind = TOK_FLOAT; + break; + case 'i': + if (tok->size == 3 && !memcmp(p, "int", 3)) tok->kind = TOK_INT; + break; + case 'l': + if (tok->size == 4 && !memcmp(p, "long", 4)) tok->kind = TOK_LONG; + break; + case 's': + if (tok->size == 5 && !memcmp(p, "short", 5)) tok->kind = TOK_SHORT; + if (tok->size == 6 && !memcmp(p, "signed", 6)) tok->kind = TOK_SIGNED; + if (tok->size == 6 && !memcmp(p, "struct", 6)) tok->kind = TOK_STRUCT; + break; + case 'u': + if (tok->size == 5 && !memcmp(p, "union", 5)) tok->kind = TOK_UNION; + if (tok->size == 8 && !memcmp(p,"unsigned",8)) tok->kind = TOK_UNSIGNED; + break; + case 'v': + if (tok->size == 4 && !memcmp(p, "void", 4)) tok->kind = TOK_VOID; + if (tok->size == 8 && !memcmp(p,"volatile",8)) tok->kind = TOK_VOLATILE; + break; + } +} + +static int parse_error(token_t *tok, const char *msg) +{ + if (tok->kind != TOK_ERROR) { + tok->kind = TOK_ERROR; + tok->info->error_location = tok->p - tok->input; + tok->info->error_message = msg; + } + return -1; +} + +static int write_ds(token_t *tok, _cffi_opcode_t ds) +{ + size_t index = tok->output_index; + if (index >= tok->info->output_size) { + parse_error(tok, "internal type complexity limit reached"); + return -1; + } + tok->output[index] = ds; + tok->output_index = index + 1; + return index; +} + +#define MAX_SSIZE_T (((size_t)-1) >> 1) + +static int parse_complete(token_t *tok); +static const char *get_common_type(const char *search, size_t search_len); +static int parse_common_type_replacement(token_t *tok, const char *replacement); + +static int parse_sequel(token_t *tok, int outer) +{ + /* Emit opcodes for the "sequel", which is the optional part of a + type declaration that follows the type name, i.e. everything + with '*', '[ ]', '( )'. Returns the entry point index pointing + the innermost opcode (the one that corresponds to the complete + type). The 'outer' argument is the index of the opcode outside + this "sequel". + */ + int check_for_grouping, abi=0; + _cffi_opcode_t result, *p_current; + + header: + switch (tok->kind) { + case TOK_STAR: + outer = write_ds(tok, _CFFI_OP(_CFFI_OP_POINTER, outer)); + next_token(tok); + goto header; + case TOK_CONST: + /* ignored for now */ + next_token(tok); + goto header; + case TOK_VOLATILE: + /* ignored for now */ + next_token(tok); + goto header; + case TOK_CDECL: + case TOK_STDCALL: + /* must be in a function; checked below */ + abi = tok->kind; + next_token(tok); + goto header; + default: + break; + } + + check_for_grouping = 1; + if (tok->kind == TOK_IDENTIFIER) { + next_token(tok); /* skip a potential variable name */ + check_for_grouping = 0; + } + + result = 0; + p_current = &result; + + while (tok->kind == TOK_OPEN_PAREN) { + next_token(tok); + + if (tok->kind == TOK_CDECL || tok->kind == TOK_STDCALL) { + abi = tok->kind; + next_token(tok); + } + + if ((check_for_grouping--) == 1 && (tok->kind == TOK_STAR || + tok->kind == TOK_CONST || + tok->kind == TOK_VOLATILE || + tok->kind == TOK_OPEN_BRACKET)) { + /* just parentheses for grouping. Use a OP_NOOP to simplify */ + int x; + assert(p_current == &result); + x = tok->output_index; + p_current = tok->output + x; + + write_ds(tok, _CFFI_OP(_CFFI_OP_NOOP, 0)); + + x = parse_sequel(tok, x); + result = _CFFI_OP(_CFFI_GETOP(0), x); + } + else { + /* function type */ + int arg_total, base_index, arg_next, flags=0; + + if (abi == TOK_STDCALL) { + flags = 2; + /* note that an ellipsis below will overwrite this flags, + which is the goal: variadic functions are always cdecl */ + } + abi = 0; + + if (tok->kind == TOK_VOID && get_following_char(tok) == ')') { + next_token(tok); + } + + /* (over-)estimate 'arg_total'. May return 1 when it is really 0 */ + arg_total = number_of_commas(tok) + 1; + + *p_current = _CFFI_OP(_CFFI_GETOP(*p_current), tok->output_index); + p_current = tok->output + tok->output_index; + + base_index = write_ds(tok, _CFFI_OP(_CFFI_OP_FUNCTION, 0)); + if (base_index < 0) + return -1; + /* reserve (arg_total + 1) slots for the arguments and the + final FUNCTION_END */ + for (arg_next = 0; arg_next <= arg_total; arg_next++) + if (write_ds(tok, _CFFI_OP(0, 0)) < 0) + return -1; + + arg_next = base_index + 1; + + if (tok->kind != TOK_CLOSE_PAREN) { + while (1) { + int arg; + _cffi_opcode_t oarg; + + if (tok->kind == TOK_DOTDOTDOT) { + flags = 1; /* ellipsis */ + next_token(tok); + break; + } + arg = parse_complete(tok); + switch (_CFFI_GETOP(tok->output[arg])) { + case _CFFI_OP_ARRAY: + case _CFFI_OP_OPEN_ARRAY: + arg = _CFFI_GETARG(tok->output[arg]); + /* fall-through */ + case _CFFI_OP_FUNCTION: + oarg = _CFFI_OP(_CFFI_OP_POINTER, arg); + break; + default: + oarg = _CFFI_OP(_CFFI_OP_NOOP, arg); + break; + } + assert(arg_next - base_index <= arg_total); + tok->output[arg_next++] = oarg; + if (tok->kind != TOK_COMMA) + break; + next_token(tok); + } + } + tok->output[arg_next] = _CFFI_OP(_CFFI_OP_FUNCTION_END, flags); + } + + if (tok->kind != TOK_CLOSE_PAREN) + return parse_error(tok, "expected ')'"); + next_token(tok); + } + + if (abi != 0) + return parse_error(tok, "expected '('"); + + while (tok->kind == TOK_OPEN_BRACKET) { + *p_current = _CFFI_OP(_CFFI_GETOP(*p_current), tok->output_index); + p_current = tok->output + tok->output_index; + + next_token(tok); + if (tok->kind != TOK_CLOSE_BRACKET) { + size_t length; + int gindex; + char *endptr; + + switch (tok->kind) { + + case TOK_INTEGER: + errno = 0; + if (sizeof(length) > sizeof(unsigned long)) { +#ifdef MS_WIN32 +# ifdef _WIN64 + length = _strtoui64(tok->p, &endptr, 0); +# else + abort(); /* unreachable */ +# endif +#else + length = strtoull(tok->p, &endptr, 0); +#endif + } + else + length = strtoul(tok->p, &endptr, 0); + if (endptr != tok->p + tok->size) + return parse_error(tok, "invalid number"); + if (errno == ERANGE || length > MAX_SSIZE_T) + return parse_error(tok, "number too large"); + break; + + case TOK_IDENTIFIER: + gindex = search_in_globals(tok->info->ctx, tok->p, tok->size); + if (gindex >= 0) { + const struct _cffi_global_s *g; + g = &tok->info->ctx->globals[gindex]; + if (_CFFI_GETOP(g->type_op) == _CFFI_OP_CONSTANT_INT || + _CFFI_GETOP(g->type_op) == _CFFI_OP_ENUM) { + int neg; + struct _cffi_getconst_s gc; + gc.ctx = tok->info->ctx; + gc.gindex = gindex; + neg = ((int(*)(struct _cffi_getconst_s*))g->address) + (&gc); + if (neg == 0 && gc.value > MAX_SSIZE_T) + return parse_error(tok, + "integer constant too large"); + if (neg == 0 || gc.value == 0) { + length = (size_t)gc.value; + break; + } + if (neg != 1) + return parse_error(tok, "disagreement about" + " this constant's value"); + } + } + /* fall-through to the default case */ + default: + return parse_error(tok, "expected a positive integer constant"); + } + + next_token(tok); + + write_ds(tok, _CFFI_OP(_CFFI_OP_ARRAY, 0)); + write_ds(tok, (_cffi_opcode_t)length); + } + else + write_ds(tok, _CFFI_OP(_CFFI_OP_OPEN_ARRAY, 0)); + + if (tok->kind != TOK_CLOSE_BRACKET) + return parse_error(tok, "expected ']'"); + next_token(tok); + } + + *p_current = _CFFI_OP(_CFFI_GETOP(*p_current), outer); + return _CFFI_GETARG(result); +} + +static int search_sorted(const char *const *base, + size_t item_size, int array_len, + const char *search, size_t search_len) +{ + int left = 0, right = array_len; + const char *baseptr = (const char *)base; + + while (left < right) { + int middle = (left + right) / 2; + const char *src = *(const char *const *)(baseptr + middle * item_size); + int diff = strncmp(src, search, search_len); + if (diff == 0 && src[search_len] == '\0') + return middle; + else if (diff >= 0) + right = middle; + else + left = middle + 1; + } + return -1; +} + +#define MAKE_SEARCH_FUNC(FIELD) \ + static \ + int search_in_##FIELD(const struct _cffi_type_context_s *ctx, \ + const char *search, size_t search_len) \ + { \ + return search_sorted(&ctx->FIELD->name, sizeof(*ctx->FIELD), \ + ctx->num_##FIELD, search, search_len); \ + } + +MAKE_SEARCH_FUNC(globals) +MAKE_SEARCH_FUNC(struct_unions) +MAKE_SEARCH_FUNC(typenames) +MAKE_SEARCH_FUNC(enums) + +#undef MAKE_SEARCH_FUNC + + +static +int search_standard_typename(const char *p, size_t size) +{ + if (size < 6 || p[size-2] != '_' || p[size-1] != 't') + return -1; + + switch (p[4]) { + + case '1': + if (size == 8 && !memcmp(p, "uint16", 6)) return _CFFI_PRIM_UINT16; + if (size == 8 && !memcmp(p, "char16", 6)) return _CFFI_PRIM_CHAR16; + break; + + case '2': + if (size == 7 && !memcmp(p, "int32", 5)) return _CFFI_PRIM_INT32; + break; + + case '3': + if (size == 8 && !memcmp(p, "uint32", 6)) return _CFFI_PRIM_UINT32; + if (size == 8 && !memcmp(p, "char32", 6)) return _CFFI_PRIM_CHAR32; + break; + + case '4': + if (size == 7 && !memcmp(p, "int64", 5)) return _CFFI_PRIM_INT64; + break; + + case '6': + if (size == 8 && !memcmp(p, "uint64", 6)) return _CFFI_PRIM_UINT64; + if (size == 7 && !memcmp(p, "int16", 5)) return _CFFI_PRIM_INT16; + break; + + case '8': + if (size == 7 && !memcmp(p, "uint8", 5)) return _CFFI_PRIM_UINT8; + break; + + case 'a': + if (size == 8 && !memcmp(p, "intmax", 6)) return _CFFI_PRIM_INTMAX; + break; + + case 'e': + if (size == 7 && !memcmp(p, "ssize", 5)) return _CFFI_PRIM_SSIZE; + break; + + case 'f': + if (size == 11 && !memcmp(p, "int_fast8", 9)) return _CFFI_PRIM_INT_FAST8; + if (size == 12 && !memcmp(p, "int_fast16", 10)) return _CFFI_PRIM_INT_FAST16; + if (size == 12 && !memcmp(p, "int_fast32", 10)) return _CFFI_PRIM_INT_FAST32; + if (size == 12 && !memcmp(p, "int_fast64", 10)) return _CFFI_PRIM_INT_FAST64; + break; + + case 'i': + if (size == 9 && !memcmp(p, "ptrdiff", 7)) return _CFFI_PRIM_PTRDIFF; + break; + + case 'l': + if (size == 12 && !memcmp(p, "int_least8", 10)) return _CFFI_PRIM_INT_LEAST8; + if (size == 13 && !memcmp(p, "int_least16", 11)) return _CFFI_PRIM_INT_LEAST16; + if (size == 13 && !memcmp(p, "int_least32", 11)) return _CFFI_PRIM_INT_LEAST32; + if (size == 13 && !memcmp(p, "int_least64", 11)) return _CFFI_PRIM_INT_LEAST64; + break; + + case 'm': + if (size == 9 && !memcmp(p, "uintmax", 7)) return _CFFI_PRIM_UINTMAX; + break; + + case 'p': + if (size == 9 && !memcmp(p, "uintptr", 7)) return _CFFI_PRIM_UINTPTR; + break; + + case 'r': + if (size == 7 && !memcmp(p, "wchar", 5)) return _CFFI_PRIM_WCHAR; + break; + + case 't': + if (size == 8 && !memcmp(p, "intptr", 6)) return _CFFI_PRIM_INTPTR; + break; + + case '_': + if (size == 6 && !memcmp(p, "size", 4)) return _CFFI_PRIM_SIZE; + if (size == 6 && !memcmp(p, "int8", 4)) return _CFFI_PRIM_INT8; + if (size >= 12) { + switch (p[10]) { + case '1': + if (size == 14 && !memcmp(p, "uint_least16", 12)) return _CFFI_PRIM_UINT_LEAST16; + break; + case '2': + if (size == 13 && !memcmp(p, "uint_fast32", 11)) return _CFFI_PRIM_UINT_FAST32; + break; + case '3': + if (size == 14 && !memcmp(p, "uint_least32", 12)) return _CFFI_PRIM_UINT_LEAST32; + break; + case '4': + if (size == 13 && !memcmp(p, "uint_fast64", 11)) return _CFFI_PRIM_UINT_FAST64; + break; + case '6': + if (size == 14 && !memcmp(p, "uint_least64", 12)) return _CFFI_PRIM_UINT_LEAST64; + if (size == 13 && !memcmp(p, "uint_fast16", 11)) return _CFFI_PRIM_UINT_FAST16; + break; + case '8': + if (size == 13 && !memcmp(p, "uint_least8", 11)) return _CFFI_PRIM_UINT_LEAST8; + break; + case '_': + if (size == 12 && !memcmp(p, "uint_fast8", 10)) return _CFFI_PRIM_UINT_FAST8; + break; + default: + break; + } + } + break; + + default: + break; + } + return -1; +} + + +static int parse_complete(token_t *tok) +{ + unsigned int t0; + _cffi_opcode_t t1; + _cffi_opcode_t t1complex; + int modifiers_length, modifiers_sign; + + qualifiers: + switch (tok->kind) { + case TOK_CONST: + /* ignored for now */ + next_token(tok); + goto qualifiers; + case TOK_VOLATILE: + /* ignored for now */ + next_token(tok); + goto qualifiers; + default: + ; + } + + modifiers_length = 0; + modifiers_sign = 0; + modifiers: + switch (tok->kind) { + + case TOK_SHORT: + if (modifiers_length != 0) + return parse_error(tok, "'short' after another 'short' or 'long'"); + modifiers_length--; + next_token(tok); + goto modifiers; + + case TOK_LONG: + if (modifiers_length < 0) + return parse_error(tok, "'long' after 'short'"); + if (modifiers_length >= 2) + return parse_error(tok, "'long long long' is too long"); + modifiers_length++; + next_token(tok); + goto modifiers; + + case TOK_SIGNED: + if (modifiers_sign) + return parse_error(tok, "multiple 'signed' or 'unsigned'"); + modifiers_sign++; + next_token(tok); + goto modifiers; + + case TOK_UNSIGNED: + if (modifiers_sign) + return parse_error(tok, "multiple 'signed' or 'unsigned'"); + modifiers_sign--; + next_token(tok); + goto modifiers; + + default: + break; + } + + t1complex = 0; + + if (modifiers_length || modifiers_sign) { + + switch (tok->kind) { + + case TOK_VOID: + case TOK__BOOL: + case TOK_FLOAT: + case TOK_STRUCT: + case TOK_UNION: + case TOK_ENUM: + case TOK__COMPLEX: + return parse_error(tok, "invalid combination of types"); + + case TOK_DOUBLE: + if (modifiers_sign != 0 || modifiers_length != 1) + return parse_error(tok, "invalid combination of types"); + next_token(tok); + t0 = _CFFI_PRIM_LONGDOUBLE; + break; + + case TOK_CHAR: + if (modifiers_length != 0) + return parse_error(tok, "invalid combination of types"); + modifiers_length = -2; + /* fall-through */ + case TOK_INT: + next_token(tok); + /* fall-through */ + default: + if (modifiers_sign >= 0) + switch (modifiers_length) { + case -2: t0 = _CFFI_PRIM_SCHAR; break; + case -1: t0 = _CFFI_PRIM_SHORT; break; + case 1: t0 = _CFFI_PRIM_LONG; break; + case 2: t0 = _CFFI_PRIM_LONGLONG; break; + default: t0 = _CFFI_PRIM_INT; break; + } + else + switch (modifiers_length) { + case -2: t0 = _CFFI_PRIM_UCHAR; break; + case -1: t0 = _CFFI_PRIM_USHORT; break; + case 1: t0 = _CFFI_PRIM_ULONG; break; + case 2: t0 = _CFFI_PRIM_ULONGLONG; break; + default: t0 = _CFFI_PRIM_UINT; break; + } + } + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, t0); + } + else { + switch (tok->kind) { + case TOK_INT: + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_INT); + break; + case TOK_CHAR: + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_CHAR); + break; + case TOK_VOID: + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_VOID); + break; + case TOK__BOOL: + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_BOOL); + break; + case TOK_FLOAT: + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_FLOAT); + t1complex = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_FLOATCOMPLEX); + break; + case TOK_DOUBLE: + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_DOUBLE); + t1complex = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_DOUBLECOMPLEX); + break; + case TOK_IDENTIFIER: + { + const char *replacement; + int n = search_in_typenames(tok->info->ctx, tok->p, tok->size); + if (n >= 0) { + t1 = _CFFI_OP(_CFFI_OP_TYPENAME, n); + break; + } + n = search_standard_typename(tok->p, tok->size); + if (n >= 0) { + t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, n); + break; + } + replacement = get_common_type(tok->p, tok->size); + if (replacement != NULL) { + n = parse_common_type_replacement(tok, replacement); + if (n < 0) + return parse_error(tok, "internal error, please report!"); + t1 = _CFFI_OP(_CFFI_OP_NOOP, n); + break; + } + return parse_error(tok, "undefined type name"); + } + case TOK_STRUCT: + case TOK_UNION: + { + int n, kind = tok->kind; + next_token(tok); + if (tok->kind != TOK_IDENTIFIER) + return parse_error(tok, "struct or union name expected"); + + n = search_in_struct_unions(tok->info->ctx, tok->p, tok->size); + if (n < 0) { + if (kind == TOK_STRUCT && tok->size == 8 && + !memcmp(tok->p, "_IO_FILE", 8)) + n = _CFFI__IO_FILE_STRUCT; + else + return parse_error(tok, "undefined struct/union name"); + } + else if (((tok->info->ctx->struct_unions[n].flags & _CFFI_F_UNION) + != 0) ^ (kind == TOK_UNION)) + return parse_error(tok, "wrong kind of tag: struct vs union"); + + t1 = _CFFI_OP(_CFFI_OP_STRUCT_UNION, n); + break; + } + case TOK_ENUM: + { + int n; + next_token(tok); + if (tok->kind != TOK_IDENTIFIER) + return parse_error(tok, "enum name expected"); + + n = search_in_enums(tok->info->ctx, tok->p, tok->size); + if (n < 0) + return parse_error(tok, "undefined enum name"); + + t1 = _CFFI_OP(_CFFI_OP_ENUM, n); + break; + } + default: + return parse_error(tok, "identifier expected"); + } + next_token(tok); + } + if (tok->kind == TOK__COMPLEX) + { + if (t1complex == 0) + return parse_error(tok, "_Complex type combination unsupported"); + t1 = t1complex; + next_token(tok); + } + + return parse_sequel(tok, write_ds(tok, t1)); +} + + +static +int parse_c_type_from(struct _cffi_parse_info_s *info, size_t *output_index, + const char *input) +{ + int result; + token_t token; + + token.info = info; + token.kind = TOK_START; + token.input = input; + token.p = input; + token.size = 0; + token.output = info->output; + token.output_index = *output_index; + + next_token(&token); + result = parse_complete(&token); + + *output_index = token.output_index; + if (token.kind != TOK_END) + return parse_error(&token, "unexpected symbol"); + return result; +} + +static +int parse_c_type(struct _cffi_parse_info_s *info, const char *input) +{ + size_t output_index = 0; + return parse_c_type_from(info, &output_index, input); +} + +static +int parse_common_type_replacement(token_t *tok, const char *replacement) +{ + return parse_c_type_from(tok->info, &tok->output_index, replacement); +} diff --git a/c/realize_c_type.c b/c/realize_c_type.c new file mode 100644 index 0000000..082c488 --- /dev/null +++ b/c/realize_c_type.c @@ -0,0 +1,797 @@ + +typedef struct { + struct _cffi_type_context_s ctx; /* inlined substructure */ + PyObject *types_dict; + PyObject *included_ffis; + PyObject *included_libs; + PyObject *_keepalive1; + PyObject *_keepalive2; +} builder_c_t; + + +static PyObject *all_primitives[_CFFI__NUM_PRIM]; +static CTypeDescrObject *g_ct_voidp, *g_ct_chararray; + +static PyObject *build_primitive_type(int num); /* forward */ + +#define primitive_in_range(num) ((num) >= 0 && (num) < _CFFI__NUM_PRIM) +#define get_primitive_type(num) \ + ((primitive_in_range(num) && all_primitives[num] != NULL) ? \ + all_primitives[num] : build_primitive_type(num)) + +static int init_global_types_dict(PyObject *ffi_type_dict) +{ + int err; + PyObject *ct_void, *ct_char, *ct2, *pnull; + /* XXX some leaks in case these functions fail, but well, + MemoryErrors during importing an extension module are kind + of bad anyway */ + + ct_void = get_primitive_type(_CFFI_PRIM_VOID); // 'void' + if (ct_void == NULL) + return -1; + + ct2 = new_pointer_type((CTypeDescrObject *)ct_void); // 'void *' + if (ct2 == NULL) + return -1; + g_ct_voidp = (CTypeDescrObject *)ct2; + + ct_char = get_primitive_type(_CFFI_PRIM_CHAR); // 'char' + if (ct_char == NULL) + return -1; + + ct2 = new_pointer_type((CTypeDescrObject *)ct_char); // 'char *' + if (ct2 == NULL) + return -1; + + ct2 = new_array_type((CTypeDescrObject *)ct2, -1); // 'char[]' + if (ct2 == NULL) + return -1; + g_ct_chararray = (CTypeDescrObject *)ct2; + + pnull = new_simple_cdata(NULL, g_ct_voidp); + if (pnull == NULL) + return -1; + err = PyDict_SetItemString(ffi_type_dict, "NULL", pnull); + Py_DECREF(pnull); + return err; +} + +static void free_builder_c(builder_c_t *builder, int ctx_is_static) +{ + if (!ctx_is_static) { + size_t i; + const void *mem[] = {builder->ctx.types, + builder->ctx.globals, + builder->ctx.struct_unions, + //builder->ctx.fields: allocated with struct_unions + builder->ctx.enums, + builder->ctx.typenames}; + for (i = 0; i < sizeof(mem) / sizeof(*mem); i++) { + if (mem[i] != NULL) + PyMem_Free((void *)mem[i]); + } + } + Py_XDECREF(builder->included_ffis); + Py_XDECREF(builder->included_libs); + Py_XDECREF(builder->types_dict); + Py_XDECREF(builder->_keepalive1); + Py_XDECREF(builder->_keepalive2); +} + +static int init_builder_c(builder_c_t *builder, + const struct _cffi_type_context_s *ctx) +{ + PyObject *ldict = PyDict_New(); + if (ldict == NULL) + return -1; + + if (ctx) + builder->ctx = *ctx; + else + memset(&builder->ctx, 0, sizeof(builder->ctx)); + + builder->types_dict = ldict; + builder->included_ffis = NULL; + builder->included_libs = NULL; + builder->_keepalive1 = NULL; + builder->_keepalive2 = NULL; + return 0; +} + +static PyObject *build_primitive_type(int num) +{ + /* XXX too many translations between here and new_primitive_type() */ + static const char *primitive_name[] = { + NULL, + "_Bool", + "char", + "signed char", + "unsigned char", + "short", + "unsigned short", + "int", + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", + "long double", + "wchar_t", + "int8_t", + "uint8_t", + "int16_t", + "uint16_t", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "intptr_t", + "uintptr_t", + "ptrdiff_t", + "size_t", + "ssize_t", + "int_least8_t", + "uint_least8_t", + "int_least16_t", + "uint_least16_t", + "int_least32_t", + "uint_least32_t", + "int_least64_t", + "uint_least64_t", + "int_fast8_t", + "uint_fast8_t", + "int_fast16_t", + "uint_fast16_t", + "int_fast32_t", + "uint_fast32_t", + "int_fast64_t", + "uint_fast64_t", + "intmax_t", + "uintmax_t", + "float _Complex", + "double _Complex", + "char16_t", + "char32_t", + }; + PyObject *x; + + assert(sizeof(primitive_name) == sizeof(*primitive_name) * _CFFI__NUM_PRIM); + if (num == _CFFI_PRIM_VOID) { + x = new_void_type(); + } + else if (primitive_in_range(num) && primitive_name[num] != NULL) { + x = new_primitive_type(primitive_name[num]); + } + else if (num == _CFFI__UNKNOWN_PRIM) { + PyErr_SetString(FFIError, "primitive integer type with an unexpected " + "size (or not an integer type at all)"); + return NULL; + } + else if (num == _CFFI__UNKNOWN_FLOAT_PRIM) { + PyErr_SetString(FFIError, "primitive floating-point type with an " + "unexpected size (or not a float type at all)"); + return NULL; + } + else if (num == _CFFI__UNKNOWN_LONG_DOUBLE) { + PyErr_SetString(FFIError, "primitive floating-point type is " + "'long double', not supported for now with " + "the syntax 'typedef double... xxx;'"); + return NULL; + } + else { + PyErr_Format(PyExc_NotImplementedError, "prim=%d", num); + return NULL; + } + + all_primitives[num] = x; + return x; +} + +static PyObject *realize_global_int(builder_c_t *builder, int gindex) +{ + int neg; + char got[64]; + unsigned long long value; + struct _cffi_getconst_s gc; + const struct _cffi_global_s *g = &builder->ctx.globals[gindex]; + gc.ctx = &builder->ctx; + gc.gindex = gindex; + /* note: we cast g->address to this function type; we do the same + in parse_c_type:parse_sequel() too. Note that the called function + may be declared simply with "unsigned long long *" as argument, + which is fine as it is the first field in _cffi_getconst_s. */ + assert(&gc.value == (unsigned long long *)&gc); + neg = ((int(*)(struct _cffi_getconst_s *))g->address)(&gc); + value = gc.value; + + switch (neg) { + + case 0: + if (value <= (unsigned long long)LONG_MAX) + return PyInt_FromLong((long)value); + else + return PyLong_FromUnsignedLongLong(value); + + case 1: + if ((long long)value >= (long long)LONG_MIN) + return PyInt_FromLong((long)value); + else + return PyLong_FromLongLong((long long)value); + + default: + break; + } + if (neg == 2) + sprintf(got, "%llu (0x%llx)", value, value); + else + sprintf(got, "%lld", (long long)value); + PyErr_Format(FFIError, "the C compiler says '%.200s' is equal to %s, " + "but the cdef disagrees", g->name, got); + return NULL; +} + +static CTypeDescrObject * +unwrap_fn_as_fnptr(PyObject *x) +{ + assert(PyTuple_Check(x)); + return (CTypeDescrObject *)PyTuple_GET_ITEM(x, 0); +} + +static CTypeDescrObject * +unexpected_fn_type(PyObject *x) +{ + CTypeDescrObject *ct = unwrap_fn_as_fnptr(x); + char *text1 = ct->ct_name; + char *text2 = text1 + ct->ct_name_position + 1; + assert(text2[-3] == '('); + text2[-3] = '\0'; + PyErr_Format(FFIError, "the type '%s%s' is a function type, not a " + "pointer-to-function type", text1, text2); + text2[-3] = '('; + return NULL; +} + +static PyObject * +realize_c_type_or_func(builder_c_t *builder, + _cffi_opcode_t opcodes[], int index); /* forward */ + + +/* Interpret an opcodes[] array. If opcodes == ctx->types, store all + the intermediate types back in the opcodes[]. Returns a new + reference. +*/ +static CTypeDescrObject * +realize_c_type(builder_c_t *builder, _cffi_opcode_t opcodes[], int index) +{ + PyObject *x = realize_c_type_or_func(builder, opcodes, index); + if (x == NULL || CTypeDescr_Check(x)) + return (CTypeDescrObject *)x; + else { + unexpected_fn_type(x); + Py_DECREF(x); + return NULL; + } +} + +static void _realize_name(char *target, const char *prefix, const char *srcname) +{ + /* "xyz" => "struct xyz" + "$xyz" => "xyz" + "$1" => "struct $1" + */ + if (srcname[0] == '$' && srcname[1] != '$' && + !('0' <= srcname[1] && srcname[1] <= '9')) { + strcpy(target, &srcname[1]); + } + else { + strcpy(target, prefix); + strcat(target, srcname); + } +} + +static void _unrealize_name(char *target, const char *srcname) +{ + /* reverse of _realize_name() */ + if (strncmp(srcname, "struct ", 7) == 0) { + strcpy(target, &srcname[7]); + } + else if (strncmp(srcname, "union ", 6) == 0) { + strcpy(target, &srcname[6]); + } + else if (strncmp(srcname, "enum ", 5) == 0) { + strcpy(target, &srcname[5]); + } + else { + strcpy(target, "$"); + strcat(target, srcname); + } +} + +static PyObject * /* forward */ +_fetch_external_struct_or_union(const struct _cffi_struct_union_s *s, + PyObject *included_ffis, int recursion); + +static PyObject * +_realize_c_struct_or_union(builder_c_t *builder, int sindex) +{ + PyObject *x; + _cffi_opcode_t op2; + const struct _cffi_struct_union_s *s; + + if (sindex == _CFFI__IO_FILE_STRUCT) { + /* returns a single global cached opaque type */ + static PyObject *file_struct = NULL; + if (file_struct == NULL) + file_struct = new_struct_or_union_type("FILE", + CT_STRUCT | CT_IS_FILE); + Py_XINCREF(file_struct); + return file_struct; + } + + s = &builder->ctx.struct_unions[sindex]; + op2 = builder->ctx.types[s->type_index]; + if ((((uintptr_t)op2) & 1) == 0) { + x = (PyObject *)op2; /* found already in the "primary" slot */ + Py_INCREF(x); + } + else { + CTypeDescrObject *ct = NULL; + + if (!(s->flags & _CFFI_F_EXTERNAL)) { + int flags = (s->flags & _CFFI_F_UNION) ? CT_UNION : CT_STRUCT; + char *name = alloca(8 + strlen(s->name)); + _realize_name(name, + (s->flags & _CFFI_F_UNION) ? "union " : "struct ", + s->name); + if (strcmp(name, "struct _IO_FILE") == 0) + x = _realize_c_struct_or_union(builder, _CFFI__IO_FILE_STRUCT); + else + x = new_struct_or_union_type(name, flags); + if (x == NULL) + return NULL; + + if (!(s->flags & _CFFI_F_OPAQUE)) { + assert(s->first_field_index >= 0); + ct = (CTypeDescrObject *)x; + ct->ct_size = (Py_ssize_t)s->size; + ct->ct_length = s->alignment; /* may be -1 */ + ct->ct_flags &= ~CT_IS_OPAQUE; + ct->ct_flags |= CT_LAZY_FIELD_LIST; + ct->ct_extra = builder; + } + else + assert(s->first_field_index < 0); + } + else { + assert(s->first_field_index < 0); + x = _fetch_external_struct_or_union(s, builder->included_ffis, 0); + if (x == NULL) { + if (!PyErr_Occurred()) + PyErr_Format(FFIError, "'%s %.200s' should come from " + "ffi.include() but was not found", + (s->flags & _CFFI_F_UNION) ? "union" + : "struct", s->name); + return NULL; + } + if (!(s->flags & _CFFI_F_OPAQUE)) { + if (((CTypeDescrObject *)x)->ct_flags & CT_IS_OPAQUE) { + const char *prefix = (s->flags & _CFFI_F_UNION) ? "union" + : "struct"; + PyErr_Format(PyExc_NotImplementedError, + "'%s %.200s' is opaque in the ffi.include(), " + "but no longer in the ffi doing the include " + "(workaround: don't use ffi.include() but " + "duplicate the declarations of everything " + "using %s %.200s)", + prefix, s->name, prefix, s->name); + Py_DECREF(x); + return NULL; + } + } + } + + /* Update the "primary" OP_STRUCT_UNION slot */ + assert((((uintptr_t)x) & 1) == 0); + assert(builder->ctx.types[s->type_index] == op2); + Py_INCREF(x); + builder->ctx.types[s->type_index] = x; + + if (ct != NULL && s->size == (size_t)-2) { + /* oops, this struct is unnamed and we couldn't generate + a C expression to get its size. We have to rely on + complete_struct_or_union() to compute it now. */ + if (do_realize_lazy_struct(ct) < 0) { + builder->ctx.types[s->type_index] = op2; + return NULL; + } + } + } + return x; +} + +static PyObject * +realize_c_type_or_func(builder_c_t *builder, + _cffi_opcode_t opcodes[], int index) +{ + PyObject *x, *y, *z; + _cffi_opcode_t op = opcodes[index]; + Py_ssize_t length = -1; + + if ((((uintptr_t)op) & 1) == 0) { + x = (PyObject *)op; + Py_INCREF(x); + return x; + } + + switch (_CFFI_GETOP(op)) { + + case _CFFI_OP_PRIMITIVE: + x = get_primitive_type(_CFFI_GETARG(op)); + Py_XINCREF(x); + break; + + case _CFFI_OP_POINTER: + y = realize_c_type_or_func(builder, opcodes, _CFFI_GETARG(op)); + if (y == NULL) + return NULL; + if (CTypeDescr_Check(y)) { + x = new_pointer_type((CTypeDescrObject *)y); + } + else { + assert(PyTuple_Check(y)); /* from _CFFI_OP_FUNCTION */ + x = PyTuple_GET_ITEM(y, 0); + Py_INCREF(x); + } + Py_DECREF(y); + break; + + case _CFFI_OP_ARRAY: + length = (Py_ssize_t)opcodes[index + 1]; + /* fall-through */ + case _CFFI_OP_OPEN_ARRAY: + y = (PyObject *)realize_c_type(builder, opcodes, _CFFI_GETARG(op)); + if (y == NULL) + return NULL; + z = new_pointer_type((CTypeDescrObject *)y); + Py_DECREF(y); + if (z == NULL) + return NULL; + x = new_array_type((CTypeDescrObject *)z, length); + Py_DECREF(z); + break; + + case _CFFI_OP_STRUCT_UNION: + x = _realize_c_struct_or_union(builder, _CFFI_GETARG(op)); + break; + + case _CFFI_OP_ENUM: + { + const struct _cffi_enum_s *e; + _cffi_opcode_t op2; + + e = &builder->ctx.enums[_CFFI_GETARG(op)]; + op2 = builder->ctx.types[e->type_index]; + if ((((uintptr_t)op2) & 1) == 0) { + x = (PyObject *)op2; + Py_INCREF(x); + } + else { + PyObject *enumerators = NULL, *enumvalues = NULL, *tmp; + Py_ssize_t i, j, n = 0; + const char *p; + int gindex; + PyObject *args; + PyObject *basetd = get_primitive_type(e->type_prim); + if (basetd == NULL) + return NULL; + + if (*e->enumerators != '\0') { + n++; + for (p = e->enumerators; *p != '\0'; p++) + n += (*p == ','); + } + enumerators = PyTuple_New(n); + if (enumerators == NULL) + return NULL; + + enumvalues = PyTuple_New(n); + if (enumvalues == NULL) { + Py_DECREF(enumerators); + return NULL; + } + + p = e->enumerators; + for (i = 0; i < n; i++) { + j = 0; + while (p[j] != ',' && p[j] != '\0') + j++; + tmp = PyText_FromStringAndSize(p, j); + if (tmp == NULL) + break; + PyTuple_SET_ITEM(enumerators, i, tmp); + + gindex = search_in_globals(&builder->ctx, p, j); + assert(gindex >= 0); + assert(builder->ctx.globals[gindex].type_op == + _CFFI_OP(_CFFI_OP_ENUM, -1)); + + tmp = realize_global_int(builder, gindex); + if (tmp == NULL) + break; + PyTuple_SET_ITEM(enumvalues, i, tmp); + + p += j + 1; + } + + args = NULL; + if (!PyErr_Occurred()) { + char *name = alloca(6 + strlen(e->name)); + _realize_name(name, "enum ", e->name); + args = Py_BuildValue("(sOOO)", name, enumerators, + enumvalues, basetd); + } + Py_DECREF(enumerators); + Py_DECREF(enumvalues); + if (args == NULL) + return NULL; + + x = b_new_enum_type(NULL, args); + Py_DECREF(args); + if (x == NULL) + return NULL; + + /* Update the "primary" _CFFI_OP_ENUM slot, which + may be the same or a different slot than the "current" one */ + assert((((uintptr_t)x) & 1) == 0); + assert(builder->ctx.types[e->type_index] == op2); + Py_INCREF(x); + builder->ctx.types[e->type_index] = x; + + /* Done, leave without updating the "current" slot because + it may be done already above. If not, never mind, the + next call to realize_c_type() will do it. */ + return x; + } + break; + } + + case _CFFI_OP_FUNCTION: + { + PyObject *fargs; + int i, base_index, num_args, ellipsis, abi; + + y = (PyObject *)realize_c_type(builder, opcodes, _CFFI_GETARG(op)); + if (y == NULL) + return NULL; + + base_index = index + 1; + num_args = 0; + /* note that if the arguments are already built, they have a + pointer in the 'opcodes' array, and GETOP() returns a + random even value. But OP_FUNCTION_END is odd, so the + condition below still works correctly. */ + while (_CFFI_GETOP(opcodes[base_index + num_args]) != + _CFFI_OP_FUNCTION_END) + num_args++; + + ellipsis = _CFFI_GETARG(opcodes[base_index + num_args]) & 0x01; + abi = _CFFI_GETARG(opcodes[base_index + num_args]) & 0xFE; + switch (abi) { + case 0: + abi = FFI_DEFAULT_ABI; + break; + case 2: +#if defined(MS_WIN32) && !defined(_WIN64) + abi = FFI_STDCALL; +#else + abi = FFI_DEFAULT_ABI; +#endif + break; + default: + PyErr_Format(FFIError, "abi number %d not supported", abi); + Py_DECREF(y); + return NULL; + } + + fargs = PyTuple_New(num_args); + if (fargs == NULL) { + Py_DECREF(y); + return NULL; + } + + for (i = 0; i < num_args; i++) { + z = (PyObject *)realize_c_type(builder, opcodes, base_index + i); + if (z == NULL) { + Py_DECREF(fargs); + Py_DECREF(y); + return NULL; + } + PyTuple_SET_ITEM(fargs, i, z); + } + + z = new_function_type(fargs, (CTypeDescrObject *)y, ellipsis, abi); + Py_DECREF(fargs); + Py_DECREF(y); + if (z == NULL) + return NULL; + + x = PyTuple_Pack(1, z); /* hack: hide the CT_FUNCTIONPTR. it will + be revealed again by the OP_POINTER */ + Py_DECREF(z); + break; + } + + case _CFFI_OP_NOOP: + x = realize_c_type_or_func(builder, opcodes, _CFFI_GETARG(op)); + break; + + case _CFFI_OP_TYPENAME: + { + /* essential: the TYPENAME opcode resolves the type index looked + up in the 'ctx->typenames' array, but it does so in 'ctx->types' + instead of in 'opcodes'! */ + int type_index = builder->ctx.typenames[_CFFI_GETARG(op)].type_index; + x = realize_c_type_or_func(builder, builder->ctx.types, type_index); + break; + } + + default: + PyErr_Format(PyExc_NotImplementedError, "op=%d", (int)_CFFI_GETOP(op)); + return NULL; + } + + if (x != NULL && opcodes == builder->ctx.types && opcodes[index] != x) { + assert((((uintptr_t)x) & 1) == 0); + assert((((uintptr_t)opcodes[index]) & 1) == 1); + Py_INCREF(x); + opcodes[index] = x; + } + return x; +}; + +static CTypeDescrObject * +realize_c_func_return_type(builder_c_t *builder, + _cffi_opcode_t opcodes[], int index) +{ + PyObject *x; + _cffi_opcode_t op = opcodes[index]; + + if ((((uintptr_t)op) & 1) == 0) { + /* already built: assert that it is a function and fish + for the return type */ + x = (PyObject *)op; + assert(PyTuple_Check(x)); /* from _CFFI_OP_FUNCTION */ + x = PyTuple_GET_ITEM(x, 0); + assert(CTypeDescr_Check(x)); + assert(((CTypeDescrObject *)x)->ct_flags & CT_FUNCTIONPTR); + x = PyTuple_GET_ITEM(((CTypeDescrObject *)x)->ct_stuff, 1); + assert(CTypeDescr_Check(x)); + Py_INCREF(x); + return (CTypeDescrObject *)x; + } + else { + assert(_CFFI_GETOP(op) == _CFFI_OP_FUNCTION); + return realize_c_type(builder, opcodes, _CFFI_GETARG(opcodes[index])); + } +} + +static int do_realize_lazy_struct(CTypeDescrObject *ct) +{ + /* This is called by force_lazy_struct() in _cffi_backend.c */ + assert(ct->ct_flags & (CT_STRUCT | CT_UNION)); + + if (ct->ct_flags & CT_LAZY_FIELD_LIST) { + builder_c_t *builder; + char *p; + int n, i, sflags; + const struct _cffi_struct_union_s *s; + const struct _cffi_field_s *fld; + PyObject *fields, *args, *res; + + assert(!(ct->ct_flags & CT_IS_OPAQUE)); + + builder = ct->ct_extra; + assert(builder != NULL); + + p = alloca(2 + strlen(ct->ct_name)); + _unrealize_name(p, ct->ct_name); + + n = search_in_struct_unions(&builder->ctx, p, strlen(p)); + if (n < 0) + Py_FatalError("lost a struct/union!"); + + s = &builder->ctx.struct_unions[n]; + fld = &builder->ctx.fields[s->first_field_index]; + + /* XXX painfully build all the Python objects that are the args + to b_complete_struct_or_union() */ + + fields = PyList_New(s->num_fields); + if (fields == NULL) + return -1; + + for (i = 0; i < s->num_fields; i++, fld++) { + _cffi_opcode_t op = fld->field_type_op; + int fbitsize = -1; + PyObject *f; + CTypeDescrObject *ctf; + + switch (_CFFI_GETOP(op)) { + + case _CFFI_OP_BITFIELD: + assert(fld->field_size >= 0); + fbitsize = (int)fld->field_size; + /* fall-through */ + case _CFFI_OP_NOOP: + ctf = realize_c_type(builder, builder->ctx.types, + _CFFI_GETARG(op)); + break; + + default: + Py_DECREF(fields); + PyErr_Format(PyExc_NotImplementedError, "field op=%d", + (int)_CFFI_GETOP(op)); + return -1; + } + + if (ctf != NULL && fld->field_offset == (size_t)-1) { + /* unnamed struct, with field positions and sizes entirely + determined by complete_struct_or_union() and not checked. + Or, bitfields (field_size >= 0), similarly not checked. */ + assert(fld->field_size == (size_t)-1 || fbitsize >= 0); + } + else if (ctf == NULL || detect_custom_layout(ct, SF_STD_FIELD_POS, + ctf->ct_size, fld->field_size, + "wrong size for field '", + fld->name, "'") < 0) { + Py_DECREF(fields); + return -1; + } + + f = Py_BuildValue("(sOin)", fld->name, ctf, + fbitsize, (Py_ssize_t)fld->field_offset); + if (f == NULL) { + Py_DECREF(fields); + return -1; + } + PyList_SET_ITEM(fields, i, f); + } + + sflags = 0; + if (s->flags & _CFFI_F_CHECK_FIELDS) + sflags |= SF_STD_FIELD_POS; + if (s->flags & _CFFI_F_PACKED) + sflags |= SF_PACKED; + + args = Py_BuildValue("(OOOnii)", ct, fields, Py_None, + (Py_ssize_t)s->size, + s->alignment, + sflags); + Py_DECREF(fields); + if (args == NULL) + return -1; + + ct->ct_extra = NULL; + ct->ct_flags |= CT_IS_OPAQUE; + res = b_complete_struct_or_union(NULL, args); + ct->ct_flags &= ~CT_IS_OPAQUE; + Py_DECREF(args); + + if (res == NULL) { + ct->ct_extra = builder; + return -1; + } + + assert(ct->ct_stuff != NULL); + ct->ct_flags &= ~CT_LAZY_FIELD_LIST; + Py_DECREF(res); + return 1; + } + else { + assert(ct->ct_flags & CT_IS_OPAQUE); + return 0; + } +} diff --git a/c/test_c.py b/c/test_c.py new file mode 100644 index 0000000..da5f751 --- /dev/null +++ b/c/test_c.py @@ -0,0 +1,4256 @@ +import py +def _setup_path(): + import os, sys + if '__pypy__' in sys.builtin_module_names: + py.test.skip("_cffi_backend.c: not tested on top of pypy, " + "use pypy/module/_cffi_backend/test/ instead.") + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +_setup_path() +from _cffi_backend import * +from _cffi_backend import _testfunc, _get_types, _get_common_types, __version__ + +# ____________________________________________________________ + +import sys +assert __version__ == "1.12.2", ("This test_c.py file is for testing a version" + " of cffi that differs from the one that we" + " get from 'import _cffi_backend'") +if sys.version_info < (3,): + type_or_class = "type" + mandatory_b_prefix = '' + mandatory_u_prefix = 'u' + bytechr = chr + bitem2bchr = lambda x: x + class U(object): + def __add__(self, other): + return eval('u'+repr(other).replace(r'\\u', r'\u') + .replace(r'\\U', r'\U')) + u = U() + str2bytes = str + strict_compare = False +else: + type_or_class = "class" + long = int + unicode = str + unichr = chr + mandatory_b_prefix = 'b' + mandatory_u_prefix = '' + bytechr = lambda n: bytes([n]) + bitem2bchr = bytechr + u = "" + str2bytes = lambda s: bytes(s, "ascii") + strict_compare = True + +def size_of_int(): + BInt = new_primitive_type("int") + return sizeof(BInt) + +def size_of_long(): + BLong = new_primitive_type("long") + return sizeof(BLong) + +def size_of_ptr(): + BInt = new_primitive_type("int") + BPtr = new_pointer_type(BInt) + return sizeof(BPtr) + + +def find_and_load_library(name, flags=RTLD_NOW): + import ctypes.util + if name is None: + path = None + else: + path = ctypes.util.find_library(name) + if path is None and name == 'c': + assert sys.platform == 'win32' + assert sys.version_info >= (3,) + py.test.skip("dlopen(None) cannot work on Windows with Python 3") + return load_library(path, flags) + +def test_load_library(): + x = find_and_load_library('c') + assert repr(x).startswith("" + +def check_dir(p, expected): + got = [name for name in dir(p) if not name.startswith('_')] + assert got == sorted(expected) + +def test_inspect_primitive_type(): + p = new_primitive_type("signed char") + assert p.kind == "primitive" + assert p.cname == "signed char" + check_dir(p, ['cname', 'kind']) + +def test_cast_to_signed_char(): + p = new_primitive_type("signed char") + x = cast(p, -65 + 17*256) + assert repr(x) == "" + assert repr(type(x)) == "<%s '_cffi_backend.CData'>" % type_or_class + assert int(x) == -65 + x = cast(p, -66 + (1<<199)*256) + assert repr(x) == "" + assert int(x) == -66 + assert (x == cast(p, -66)) is True + assert (x != cast(p, -66)) is False + q = new_primitive_type("short") + assert (x == cast(q, -66)) is True + assert (x != cast(q, -66)) is False + +def test_sizeof_type(): + py.test.raises(TypeError, sizeof, 42.5) + p = new_primitive_type("short") + assert sizeof(p) == 2 + +def test_integer_types(): + for name in ['signed char', 'short', 'int', 'long', 'long long']: + p = new_primitive_type(name) + size = sizeof(p) + min = -(1 << (8*size-1)) + max = (1 << (8*size-1)) - 1 + assert int(cast(p, min)) == min + assert int(cast(p, max)) == max + assert int(cast(p, min - 1)) == max + assert int(cast(p, max + 1)) == min + py.test.raises(TypeError, cast, p, None) + assert long(cast(p, min - 1)) == max + assert int(cast(p, b'\x08')) == 8 + assert int(cast(p, u+'\x08')) == 8 + for name in ['char', 'short', 'int', 'long', 'long long']: + p = new_primitive_type('unsigned ' + name) + size = sizeof(p) + max = (1 << (8*size)) - 1 + assert int(cast(p, 0)) == 0 + assert int(cast(p, max)) == max + assert int(cast(p, -1)) == max + assert int(cast(p, max + 1)) == 0 + assert long(cast(p, -1)) == max + assert int(cast(p, b'\xFE')) == 254 + assert int(cast(p, u+'\xFE')) == 254 + +def test_no_float_on_int_types(): + p = new_primitive_type('long') + py.test.raises(TypeError, float, cast(p, 42)) + py.test.raises(TypeError, complex, cast(p, 42)) + +def test_float_types(): + INF = 1E200 * 1E200 + for name in ["float", "double"]: + p = new_primitive_type(name) + assert bool(cast(p, 0)) is False # since 1.7 + assert bool(cast(p, -0.0)) is False # since 1.7 + assert bool(cast(p, 1e-42)) is True + assert bool(cast(p, -1e-42)) is True + assert bool(cast(p, INF)) + assert bool(cast(p, -INF)) + assert bool(cast(p, float("nan"))) + assert int(cast(p, -150)) == -150 + assert int(cast(p, 61.91)) == 61 + assert long(cast(p, 61.91)) == 61 + assert type(int(cast(p, 61.91))) is int + assert type(int(cast(p, 1E22))) is long + assert type(long(cast(p, 61.91))) is long + assert type(long(cast(p, 1E22))) is long + py.test.raises(OverflowError, int, cast(p, INF)) + py.test.raises(OverflowError, int, cast(p, -INF)) + assert float(cast(p, 1.25)) == 1.25 + assert float(cast(p, INF)) == INF + assert float(cast(p, -INF)) == -INF + if name == "float": + assert float(cast(p, 1.1)) != 1.1 # rounding error + assert float(cast(p, 1E200)) == INF # limited range + + assert cast(p, -1.1) == cast(p, -1.1) + assert repr(float(cast(p, -0.0))) == '-0.0' + assert float(cast(p, b'\x09')) == 9.0 + assert float(cast(p, u+'\x09')) == 9.0 + assert float(cast(p, True)) == 1.0 + py.test.raises(TypeError, cast, p, None) + +def test_complex_types(): + INF = 1E200 * 1E200 + for name in ["float", "double"]: + p = new_primitive_type(name + " _Complex") + assert bool(cast(p, 0)) is False + assert bool(cast(p, INF)) + assert bool(cast(p, -INF)) + assert bool(cast(p, 0j)) is False + assert bool(cast(p, INF*1j)) + assert bool(cast(p, -INF*1j)) + # "can't convert complex to float", like CPython's "float(0j)" + py.test.raises(TypeError, int, cast(p, -150)) + py.test.raises(TypeError, long, cast(p, -150)) + py.test.raises(TypeError, float, cast(p, -150)) + assert complex(cast(p, 1.25)) == 1.25 + assert complex(cast(p, 1.25j)) == 1.25j + assert complex(cast(p, complex(0,INF))) == complex(0,INF) + assert complex(cast(p, -INF)) == -INF + if name == "float": + assert complex(cast(p, 1.1j)) != 1.1j # rounding error + assert complex(cast(p, 1E200+3j)) == INF+3j # limited range + assert complex(cast(p, complex(3,1E200))) == complex(3,INF) # limited range + + assert cast(p, -1.1j) == cast(p, -1.1j) + assert repr(complex(cast(p, -0.0)).real) == '-0.0' + #assert repr(complex(cast(p, -0j))) == '-0j' # http://bugs.python.org/issue29602 + assert complex(cast(p, b'\x09')) == 9.0 + 0j + assert complex(cast(p, u+'\x09')) == 9.0 + 0j + assert complex(cast(p, True)) == 1.0 + 0j + py.test.raises(TypeError, cast, p, None) + # + py.test.raises(TypeError, cast, new_primitive_type(name), 1+0j) + # + for basetype in ["char", "int", "uint64_t", "float", + "double", "long double"]: + baseobj = cast(new_primitive_type(basetype), 65) + py.test.raises(TypeError, complex, baseobj) + # + BArray = new_array_type(new_pointer_type(p), 10) + x = newp(BArray, None) + x[5] = 12.34 + 56.78j + assert type(x[5]) is complex + assert abs(x[5] - (12.34 + 56.78j)) < 1e-5 + assert (x[5] == 12.34 + 56.78j) == (name == "double") # rounding error + # + class Foo: + def __complex__(self): + return 2 + 3j + assert complex(Foo()) == 2 + 3j + assert complex(cast(p, Foo())) == 2 + 3j + py.test.raises(TypeError, cast, new_primitive_type("int"), 1+0j) + +def test_character_type(): + p = new_primitive_type("char") + assert bool(cast(p, 'A')) is True + assert bool(cast(p, '\x00')) is False # since 1.7 + assert cast(p, '\x00') == cast(p, -17*256) + assert int(cast(p, 'A')) == 65 + assert long(cast(p, 'A')) == 65 + assert type(int(cast(p, 'A'))) is int + assert type(long(cast(p, 'A'))) is long + assert str(cast(p, 'A')) == repr(cast(p, 'A')) + assert repr(cast(p, 'A')) == "" % mandatory_b_prefix + assert repr(cast(p, 255)) == r"" % mandatory_b_prefix + assert repr(cast(p, 0)) == r"" % mandatory_b_prefix + +def test_pointer_type(): + p = new_primitive_type("int") + assert repr(p) == "" + p = new_pointer_type(p) + assert repr(p) == "" + p = new_pointer_type(p) + assert repr(p) == "" + p = new_pointer_type(p) + assert repr(p) == "" + +def test_inspect_pointer_type(): + p1 = new_primitive_type("int") + p2 = new_pointer_type(p1) + assert p2.kind == "pointer" + assert p2.cname == "int *" + assert p2.item is p1 + check_dir(p2, ['cname', 'kind', 'item']) + p3 = new_pointer_type(p2) + assert p3.item is p2 + +def test_pointer_to_int(): + BInt = new_primitive_type("int") + py.test.raises(TypeError, newp, BInt) + py.test.raises(TypeError, newp, BInt, None) + BPtr = new_pointer_type(BInt) + p = newp(BPtr) + assert repr(p) == "" % size_of_int() + p = newp(BPtr, None) + assert repr(p) == "" % size_of_int() + p = newp(BPtr, 5000) + assert repr(p) == "" % size_of_int() + q = cast(BPtr, p) + assert repr(q).startswith("" % size_of_ptr() + +def test_reading_pointer_to_int(): + BInt = new_primitive_type("int") + BPtr = new_pointer_type(BInt) + p = newp(BPtr, None) + assert p[0] == 0 + p = newp(BPtr, 5000) + assert p[0] == 5000 + py.test.raises(IndexError, "p[1]") + py.test.raises(IndexError, "p[-1]") + +def test_reading_pointer_to_float(): + BFloat = new_primitive_type("float") + py.test.raises(TypeError, newp, BFloat, None) + BPtr = new_pointer_type(BFloat) + p = newp(BPtr, None) + assert p[0] == 0.0 and type(p[0]) is float + p = newp(BPtr, 1.25) + assert p[0] == 1.25 and type(p[0]) is float + p = newp(BPtr, 1.1) + assert p[0] != 1.1 and abs(p[0] - 1.1) < 1E-5 # rounding errors + +def test_cast_float_to_int(): + for type in ["int", "unsigned int", "long", "unsigned long", + "long long", "unsigned long long"]: + p = new_primitive_type(type) + assert int(cast(p, 4.2)) == 4 + py.test.raises(TypeError, newp, new_pointer_type(p), 4.2) + +def test_newp_integer_types(): + for name in ['signed char', 'short', 'int', 'long', 'long long']: + p = new_primitive_type(name) + pp = new_pointer_type(p) + size = sizeof(p) + min = -(1 << (8*size-1)) + max = (1 << (8*size-1)) - 1 + assert newp(pp, min)[0] == min + assert newp(pp, max)[0] == max + py.test.raises(OverflowError, newp, pp, min - 2 ** 32) + py.test.raises(OverflowError, newp, pp, min - 2 ** 64) + py.test.raises(OverflowError, newp, pp, max + 2 ** 32) + py.test.raises(OverflowError, newp, pp, max + 2 ** 64) + py.test.raises(OverflowError, newp, pp, min - 1) + py.test.raises(OverflowError, newp, pp, max + 1) + py.test.raises(OverflowError, newp, pp, min - 1 - 2 ** 32) + py.test.raises(OverflowError, newp, pp, min - 1 - 2 ** 64) + py.test.raises(OverflowError, newp, pp, max + 1) + py.test.raises(OverflowError, newp, pp, max + 1 + 2 ** 32) + py.test.raises(OverflowError, newp, pp, max + 1 + 2 ** 64) + py.test.raises(TypeError, newp, pp, 1.0) + for name in ['char', 'short', 'int', 'long', 'long long']: + p = new_primitive_type('unsigned ' + name) + pp = new_pointer_type(p) + size = sizeof(p) + max = (1 << (8*size)) - 1 + assert newp(pp, 0)[0] == 0 + assert newp(pp, max)[0] == max + py.test.raises(OverflowError, newp, pp, -1) + py.test.raises(OverflowError, newp, pp, max + 1) + +def test_reading_pointer_to_char(): + BChar = new_primitive_type("char") + py.test.raises(TypeError, newp, BChar, None) + BPtr = new_pointer_type(BChar) + p = newp(BPtr, None) + assert p[0] == b'\x00' + p = newp(BPtr, b'A') + assert p[0] == b'A' + py.test.raises(TypeError, newp, BPtr, 65) + py.test.raises(TypeError, newp, BPtr, b"foo") + py.test.raises(TypeError, newp, BPtr, u+"foo") + c = cast(BChar, b'A') + assert str(c) == repr(c) + assert int(c) == ord(b'A') + py.test.raises(TypeError, cast, BChar, b'foo') + py.test.raises(TypeError, cast, BChar, u+'foo') + e = py.test.raises(TypeError, newp, new_array_type(BPtr, None), 12.3) + assert str(e.value) == ( + "expected new array length or list/tuple/str, not float") + +def test_reading_pointer_to_pointer(): + BVoidP = new_pointer_type(new_void_type()) + BCharP = new_pointer_type(new_primitive_type("char")) + BInt = new_primitive_type("int") + BIntPtr = new_pointer_type(BInt) + BIntPtrPtr = new_pointer_type(BIntPtr) + q = newp(BIntPtr, 42) + assert q[0] == 42 + p = newp(BIntPtrPtr, None) + assert p[0] is not None + assert p[0] == cast(BVoidP, 0) + assert p[0] == cast(BCharP, 0) + assert p[0] != None + assert repr(p[0]) == "" + p[0] = q + assert p[0] != cast(BVoidP, 0) + assert p[0] != cast(BCharP, 0) + assert p[0][0] == 42 + q[0] += 1 + assert p[0][0] == 43 + p = newp(BIntPtrPtr, q) + assert p[0][0] == 43 + +def test_load_standard_library(): + if sys.platform == "win32": + py.test.raises(OSError, find_and_load_library, None) + return + x = find_and_load_library(None) + BVoidP = new_pointer_type(new_void_type()) + assert x.load_function(BVoidP, 'strcpy') + py.test.raises(AttributeError, x.load_function, + BVoidP, 'xxx_this_function_does_not_exist') + # the next one is from 'libm', not 'libc', but we assume + # that it is already loaded too, so it should work + assert x.load_function(BVoidP, 'sqrt') + # + x.close_lib() + py.test.raises(ValueError, x.load_function, BVoidP, 'sqrt') + x.close_lib() + +def test_no_len_on_nonarray(): + p = new_primitive_type("int") + py.test.raises(TypeError, len, cast(p, 42)) + +def test_cmp_none(): + p = new_primitive_type("int") + x = cast(p, 42) + assert (x == None) is False + assert (x != None) is True + assert (x == ["hello"]) is False + assert (x != ["hello"]) is True + y = cast(p, 0) + assert (y == None) is False + +def test_invalid_indexing(): + p = new_primitive_type("int") + x = cast(p, 42) + py.test.raises(TypeError, "x[0]") + +def test_default_str(): + BChar = new_primitive_type("char") + x = cast(BChar, 42) + assert str(x) == repr(x) + BInt = new_primitive_type("int") + x = cast(BInt, 42) + assert str(x) == repr(x) + BArray = new_array_type(new_pointer_type(BInt), 10) + x = newp(BArray, None) + assert str(x) == repr(x) + +def test_default_unicode(): + BInt = new_primitive_type("int") + x = cast(BInt, 42) + assert unicode(x) == unicode(repr(x)) + BArray = new_array_type(new_pointer_type(BInt), 10) + x = newp(BArray, None) + assert unicode(x) == unicode(repr(x)) + +def test_cast_from_cdataint(): + BInt = new_primitive_type("int") + x = cast(BInt, 0) + y = cast(new_pointer_type(BInt), x) + assert bool(y) is False + # + x = cast(BInt, 42) + y = cast(BInt, x) + assert int(y) == 42 + y = cast(new_primitive_type("char"), x) + assert int(y) == 42 + y = cast(new_primitive_type("float"), x) + assert float(y) == 42.0 + # + z = cast(BInt, 42.5) + assert int(z) == 42 + z = cast(BInt, y) + assert int(z) == 42 + +def test_void_type(): + p = new_void_type() + assert p.kind == "void" + assert p.cname == "void" + check_dir(p, ['kind', 'cname']) + +def test_array_type(): + p = new_primitive_type("int") + assert repr(p) == "" + # + py.test.raises(TypeError, new_array_type, new_pointer_type(p), "foo") + py.test.raises(ValueError, new_array_type, new_pointer_type(p), -42) + # + p1 = new_array_type(new_pointer_type(p), None) + assert repr(p1) == "" + py.test.raises(ValueError, new_array_type, new_pointer_type(p1), 42) + # + p1 = new_array_type(new_pointer_type(p), 42) + p2 = new_array_type(new_pointer_type(p1), 25) + assert repr(p2) == "" + p2 = new_array_type(new_pointer_type(p1), None) + assert repr(p2) == "" + # + py.test.raises(OverflowError, + new_array_type, new_pointer_type(p), sys.maxsize+1) + py.test.raises(OverflowError, + new_array_type, new_pointer_type(p), sys.maxsize // 3) + +def test_inspect_array_type(): + p = new_primitive_type("int") + p1 = new_array_type(new_pointer_type(p), None) + assert p1.kind == "array" + assert p1.cname == "int[]" + assert p1.item is p + assert p1.length is None + check_dir(p1, ['cname', 'kind', 'item', 'length']) + p1 = new_array_type(new_pointer_type(p), 42) + assert p1.kind == "array" + assert p1.cname == "int[42]" + assert p1.item is p + assert p1.length == 42 + check_dir(p1, ['cname', 'kind', 'item', 'length']) + +def test_array_instance(): + LENGTH = 1423 + p = new_primitive_type("int") + p1 = new_array_type(new_pointer_type(p), LENGTH) + a = newp(p1, None) + assert repr(a) == "" % ( + LENGTH, LENGTH * size_of_int()) + assert len(a) == LENGTH + for i in range(LENGTH): + assert a[i] == 0 + py.test.raises(IndexError, "a[LENGTH]") + py.test.raises(IndexError, "a[-1]") + for i in range(LENGTH): + a[i] = i * i + 1 + for i in range(LENGTH): + assert a[i] == i * i + 1 + e = py.test.raises(IndexError, "a[LENGTH+100] = 500") + assert ('(expected %d < %d)' % (LENGTH+100, LENGTH)) in str(e.value) + py.test.raises(TypeError, int, a) + +def test_array_of_unknown_length_instance(): + p = new_primitive_type("int") + p1 = new_array_type(new_pointer_type(p), None) + py.test.raises(TypeError, newp, p1, None) + py.test.raises(ValueError, newp, p1, -42) + a = newp(p1, 42) + assert len(a) == 42 + for i in range(42): + a[i] -= i + for i in range(42): + assert a[i] == -i + py.test.raises(IndexError, "a[42]") + py.test.raises(IndexError, "a[-1]") + py.test.raises(IndexError, "a[42] = 123") + py.test.raises(IndexError, "a[-1] = 456") + +def test_array_of_unknown_length_instance_with_initializer(): + p = new_primitive_type("int") + p1 = new_array_type(new_pointer_type(p), None) + a = newp(p1, list(range(42))) + assert len(a) == 42 + a = newp(p1, tuple(range(142))) + assert len(a) == 142 + +def test_array_initializer(): + p = new_primitive_type("int") + p1 = new_array_type(new_pointer_type(p), None) + a = newp(p1, list(range(100, 142))) + for i in range(42): + assert a[i] == 100 + i + # + p2 = new_array_type(new_pointer_type(p), 43) + a = newp(p2, tuple(range(100, 142))) + for i in range(42): + assert a[i] == 100 + i + assert a[42] == 0 # extra uninitialized item + +def test_array_add(): + p = new_primitive_type("int") + p1 = new_array_type(new_pointer_type(p), 5) # int[5] + p2 = new_array_type(new_pointer_type(p1), 3) # int[3][5] + a = newp(p2, [list(range(n, n+5)) for n in [100, 200, 300]]) + assert repr(a) == "" % ( + 3*5*size_of_int(),) + assert repr(a + 0).startswith("" + BStruct = new_struct_type("struct foo") + assert repr(BStruct) == "" + BPtr = new_pointer_type(BStruct) + assert repr(BPtr) == "" + py.test.raises(ValueError, sizeof, BStruct) + py.test.raises(ValueError, alignof, BStruct) + +def test_new_union_type(): + BUnion = new_union_type("union foo") + assert repr(BUnion) == "" + BPtr = new_pointer_type(BUnion) + assert repr(BPtr) == "" + +def test_complete_struct(): + BLong = new_primitive_type("long") + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BStruct = new_struct_type("struct foo") + assert BStruct.kind == "struct" + assert BStruct.cname == "struct foo" + assert BStruct.fields is None + check_dir(BStruct, ['cname', 'kind', 'fields']) + # + complete_struct_or_union(BStruct, [('a1', BLong, -1), + ('a2', BChar, -1), + ('a3', BShort, -1)]) + d = BStruct.fields + assert len(d) == 3 + assert d[0][0] == 'a1' + assert d[0][1].type is BLong + assert d[0][1].offset == 0 + assert d[0][1].bitshift == -1 + assert d[0][1].bitsize == -1 + assert d[1][0] == 'a2' + assert d[1][1].type is BChar + assert d[1][1].offset == sizeof(BLong) + assert d[1][1].bitshift == -1 + assert d[1][1].bitsize == -1 + assert d[2][0] == 'a3' + assert d[2][1].type is BShort + assert d[2][1].offset == sizeof(BLong) + sizeof(BShort) + assert d[2][1].bitshift == -1 + assert d[2][1].bitsize == -1 + assert sizeof(BStruct) == 2 * sizeof(BLong) + assert alignof(BStruct) == alignof(BLong) + +def test_complete_union(): + BLong = new_primitive_type("long") + BChar = new_primitive_type("char") + BUnion = new_union_type("union foo") + assert BUnion.kind == "union" + assert BUnion.cname == "union foo" + assert BUnion.fields is None + complete_struct_or_union(BUnion, [('a1', BLong, -1), + ('a2', BChar, -1)]) + d = BUnion.fields + assert len(d) == 2 + assert d[0][0] == 'a1' + assert d[0][1].type is BLong + assert d[0][1].offset == 0 + assert d[1][0] == 'a2' + assert d[1][1].type is BChar + assert d[1][1].offset == 0 + assert sizeof(BUnion) == sizeof(BLong) + assert alignof(BUnion) == alignof(BLong) + +def test_struct_instance(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + p = cast(BStructPtr, 42) + e = py.test.raises(AttributeError, "p.a1") # opaque + assert str(e.value) == ("cdata 'struct foo *' points to an opaque type: " + "cannot read fields") + e = py.test.raises(AttributeError, "p.a1 = 10") # opaque + assert str(e.value) == ("cdata 'struct foo *' points to an opaque type: " + "cannot write fields") + + complete_struct_or_union(BStruct, [('a1', BInt, -1), + ('a2', BInt, -1)]) + p = newp(BStructPtr, None) + s = p[0] + assert s.a1 == 0 + s.a2 = 123 + assert s.a1 == 0 + assert s.a2 == 123 + py.test.raises(OverflowError, "s.a1 = sys.maxsize+1") + assert s.a1 == 0 + e = py.test.raises(AttributeError, "p.foobar") + assert str(e.value) == "cdata 'struct foo *' has no field 'foobar'" + e = py.test.raises(AttributeError, "p.foobar = 42") + assert str(e.value) == "cdata 'struct foo *' has no field 'foobar'" + e = py.test.raises(AttributeError, "s.foobar") + assert str(e.value) == "cdata 'struct foo' has no field 'foobar'" + e = py.test.raises(AttributeError, "s.foobar = 42") + assert str(e.value) == "cdata 'struct foo' has no field 'foobar'" + j = cast(BInt, 42) + e = py.test.raises(AttributeError, "j.foobar") + assert str(e.value) == "cdata 'int' has no attribute 'foobar'" + e = py.test.raises(AttributeError, "j.foobar = 42") + assert str(e.value) == "cdata 'int' has no attribute 'foobar'" + j = cast(new_pointer_type(BInt), 42) + e = py.test.raises(AttributeError, "j.foobar") + assert str(e.value) == "cdata 'int *' has no attribute 'foobar'" + e = py.test.raises(AttributeError, "j.foobar = 42") + assert str(e.value) == "cdata 'int *' has no attribute 'foobar'" + pp = newp(new_pointer_type(BStructPtr), p) + e = py.test.raises(AttributeError, "pp.a1") + assert str(e.value) == "cdata 'struct foo * *' has no attribute 'a1'" + e = py.test.raises(AttributeError, "pp.a1 = 42") + assert str(e.value) == "cdata 'struct foo * *' has no attribute 'a1'" + +def test_union_instance(): + BInt = new_primitive_type("int") + BUInt = new_primitive_type("unsigned int") + BUnion = new_union_type("union bar") + complete_struct_or_union(BUnion, [('a1', BInt, -1), ('a2', BUInt, -1)]) + p = newp(new_pointer_type(BUnion), [-42]) + bigval = -42 + (1 << (8*size_of_int())) + assert p.a1 == -42 + assert p.a2 == bigval + p = newp(new_pointer_type(BUnion), {'a2': bigval}) + assert p.a1 == -42 + assert p.a2 == bigval + py.test.raises(OverflowError, newp, new_pointer_type(BUnion), + {'a1': bigval}) + p = newp(new_pointer_type(BUnion), []) + assert p.a1 == p.a2 == 0 + +def test_struct_pointer(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BInt, -1), + ('a2', BInt, -1)]) + p = newp(BStructPtr, None) + assert p.a1 == 0 # read/write via the pointer (C equivalent: '->') + p.a2 = 123 + assert p.a1 == 0 + assert p.a2 == 123 + +def test_struct_init_list(): + BVoidP = new_pointer_type(new_void_type()) + BInt = new_primitive_type("int") + BIntPtr = new_pointer_type(BInt) + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BInt, -1), + ('a2', BInt, -1), + ('a3', BInt, -1), + ('p4', BIntPtr, -1)]) + s = newp(BStructPtr, [123, 456]) + assert s.a1 == 123 + assert s.a2 == 456 + assert s.a3 == 0 + assert s.p4 == cast(BVoidP, 0) + assert s.p4 != 0 + # + s = newp(BStructPtr, {'a2': 41122, 'a3': -123}) + assert s.a1 == 0 + assert s.a2 == 41122 + assert s.a3 == -123 + assert s.p4 == cast(BVoidP, 0) + # + py.test.raises(KeyError, newp, BStructPtr, {'foobar': 0}) + # + p = newp(BIntPtr, 14141) + s = newp(BStructPtr, [12, 34, 56, p]) + assert s.p4 == p + assert s.p4 + # + s = newp(BStructPtr, [12, 34, 56, cast(BVoidP, 0)]) + assert s.p4 == cast(BVoidP, 0) + assert not s.p4 + # + py.test.raises(TypeError, newp, BStructPtr, [12, 34, 56, None]) + +def test_array_in_struct(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BArrayInt5 = new_array_type(new_pointer_type(BInt), 5) + complete_struct_or_union(BStruct, [('a1', BArrayInt5, -1)]) + s = newp(new_pointer_type(BStruct), [[20, 24, 27, 29, 30]]) + assert s.a1[2] == 27 + assert repr(s.a1).startswith("" + BFunc2 = new_function_type((), BFunc, False) + assert repr(BFunc2) == "" + +def test_inspect_function_type(): + BInt = new_primitive_type("int") + BFunc = new_function_type((BInt, BInt), BInt, False) + assert BFunc.kind == "function" + assert BFunc.cname == "int(*)(int, int)" + assert BFunc.args == (BInt, BInt) + assert BFunc.result is BInt + assert BFunc.ellipsis is False + assert BFunc.abi == FFI_DEFAULT_ABI + +def test_function_type_taking_struct(): + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, [('a1', BChar, -1), + ('a2', BShort, -1)]) + BFunc = new_function_type((BStruct,), BShort, False) + assert repr(BFunc) == "" + +def test_function_void_result(): + BVoid = new_void_type() + BInt = new_primitive_type("int") + BFunc = new_function_type((BInt, BInt), BVoid, False) + assert repr(BFunc) == "" + +def test_function_void_arg(): + BVoid = new_void_type() + BInt = new_primitive_type("int") + py.test.raises(TypeError, new_function_type, (BVoid,), BInt, False) + +def test_call_function_0(): + BSignedChar = new_primitive_type("signed char") + BFunc0 = new_function_type((BSignedChar, BSignedChar), BSignedChar, False) + f = cast(BFunc0, _testfunc(0)) + assert f(40, 2) == 42 + assert f(-100, -100) == -200 + 256 + py.test.raises(OverflowError, f, 128, 0) + py.test.raises(OverflowError, f, 0, 128) + +def test_call_function_0_pretend_bool_result(): + BSignedChar = new_primitive_type("signed char") + BBool = new_primitive_type("_Bool") + BFunc0 = new_function_type((BSignedChar, BSignedChar), BBool, False) + f = cast(BFunc0, _testfunc(0)) + assert f(40, -39) is True + assert f(40, -40) is False + py.test.raises(ValueError, f, 40, 2) + +def test_call_function_1(): + BInt = new_primitive_type("int") + BLong = new_primitive_type("long") + BFunc1 = new_function_type((BInt, BLong), BLong, False) + f = cast(BFunc1, _testfunc(1)) + assert f(40, 2) == 42 + assert f(-100, -100) == -200 + int_max = (1 << (8*size_of_int()-1)) - 1 + long_max = (1 << (8*size_of_long()-1)) - 1 + if int_max == long_max: + assert f(int_max, 1) == - int_max - 1 + else: + assert f(int_max, 1) == int_max + 1 + +def test_call_function_2(): + BLongLong = new_primitive_type("long long") + BFunc2 = new_function_type((BLongLong, BLongLong), BLongLong, False) + f = cast(BFunc2, _testfunc(2)) + longlong_max = (1 << (8*sizeof(BLongLong)-1)) - 1 + assert f(longlong_max - 42, 42) == longlong_max + assert f(43, longlong_max - 42) == - longlong_max - 1 + +def test_call_function_3(): + BFloat = new_primitive_type("float") + BDouble = new_primitive_type("double") + BFunc3 = new_function_type((BFloat, BDouble), BDouble, False) + f = cast(BFunc3, _testfunc(3)) + assert f(1.25, 5.1) == 1.25 + 5.1 # exact + res = f(1.3, 5.1) + assert res != 6.4 and abs(res - 6.4) < 1E-5 # inexact + +def test_call_function_4(): + BFloat = new_primitive_type("float") + BDouble = new_primitive_type("double") + BFunc4 = new_function_type((BFloat, BDouble), BFloat, False) + f = cast(BFunc4, _testfunc(4)) + res = f(1.25, 5.1) + assert res != 6.35 and abs(res - 6.35) < 1E-5 # inexact + +def test_call_function_5(): + BVoid = new_void_type() + BFunc5 = new_function_type((), BVoid, False) + f = cast(BFunc5, _testfunc(5)) + f() # did not crash + +def test_call_function_6(): + BInt = new_primitive_type("int") + BIntPtr = new_pointer_type(BInt) + BFunc6 = new_function_type((BIntPtr,), BIntPtr, False) + f = cast(BFunc6, _testfunc(6)) + x = newp(BIntPtr, 42) + res = f(x) + assert typeof(res) is BIntPtr + assert res[0] == 42 - 1000 + # + BIntArray = new_array_type(BIntPtr, None) + BFunc6bis = new_function_type((BIntArray,), BIntPtr, False) + f = cast(BFunc6bis, _testfunc(6)) + # + res = f([142]) + assert typeof(res) is BIntPtr + assert res[0] == 142 - 1000 + # + res = f((143,)) + assert typeof(res) is BIntPtr + assert res[0] == 143 - 1000 + # + x = newp(BIntArray, [242]) + res = f(x) + assert typeof(res) is BIntPtr + assert res[0] == 242 - 1000 + # + py.test.raises(TypeError, f, 123456) + py.test.raises(TypeError, f, "foo") + py.test.raises(TypeError, f, u+"bar") + +def test_call_function_7(): + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BChar, -1), + ('a2', BShort, -1)]) + BFunc7 = new_function_type((BStruct,), BShort, False) + f = cast(BFunc7, _testfunc(7)) + res = f({'a1': b'A', 'a2': -4042}) + assert res == -4042 + ord(b'A') + # + x = newp(BStructPtr, {'a1': b'A', 'a2': -4042}) + res = f(x[0]) + assert res == -4042 + ord(b'A') + +def test_call_function_20(): + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BChar, -1), + ('a2', BShort, -1)]) + BFunc20 = new_function_type((BStructPtr,), BShort, False) + f = cast(BFunc20, _testfunc(20)) + x = newp(BStructPtr, {'a1': b'A', 'a2': -4042}) + # can't pass a 'struct foo' + py.test.raises(TypeError, f, x[0]) + +def test_call_function_21(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, [('a', BInt, -1), + ('b', BInt, -1), + ('c', BInt, -1), + ('d', BInt, -1), + ('e', BInt, -1), + ('f', BInt, -1), + ('g', BInt, -1), + ('h', BInt, -1), + ('i', BInt, -1), + ('j', BInt, -1)]) + BFunc21 = new_function_type((BStruct,), BInt, False) + f = cast(BFunc21, _testfunc(21)) + res = f(list(range(13, 3, -1))) + lst = [(n << i) for (i, n) in enumerate(range(13, 3, -1))] + assert res == sum(lst) + +def test_call_function_22(): + BInt = new_primitive_type("int") + BArray10 = new_array_type(new_pointer_type(BInt), 10) + BStruct = new_struct_type("struct foo") + BStructP = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BArray10, -1)]) + BFunc22 = new_function_type((BStruct, BStruct), BStruct, False) + f = cast(BFunc22, _testfunc(22)) + p1 = newp(BStructP, {'a': list(range(100, 110))}) + p2 = newp(BStructP, {'a': list(range(1000, 1100, 10))}) + res = f(p1[0], p2[0]) + for i in range(10): + assert res.a[i] == p1.a[i] - p2.a[i] + +def test_call_function_23(): + BVoid = new_void_type() # declaring the function as int(void*) + BVoidP = new_pointer_type(BVoid) + BInt = new_primitive_type("int") + BFunc23 = new_function_type((BVoidP,), BInt, False) + f = cast(BFunc23, _testfunc(23)) + res = f(b"foo") + assert res == 1000 * ord(b'f') + res = f(cast(BVoidP, 0)) # NULL + assert res == -42 + py.test.raises(TypeError, f, None) + py.test.raises(TypeError, f, 0) + py.test.raises(TypeError, f, 0.0) + +def test_call_function_23_bis(): + # declaring the function as int(unsigned char*) + BUChar = new_primitive_type("unsigned char") + BUCharP = new_pointer_type(BUChar) + BInt = new_primitive_type("int") + BFunc23 = new_function_type((BUCharP,), BInt, False) + f = cast(BFunc23, _testfunc(23)) + res = f(b"foo") + assert res == 1000 * ord(b'f') + +def test_call_function_23_bool_array(): + # declaring the function as int(_Bool*) + BBool = new_primitive_type("_Bool") + BBoolP = new_pointer_type(BBool) + BInt = new_primitive_type("int") + BFunc23 = new_function_type((BBoolP,), BInt, False) + f = cast(BFunc23, _testfunc(23)) + res = f(b"\x01\x01") + assert res == 1000 + py.test.raises(ValueError, f, b"\x02\x02") + +def test_cannot_pass_struct_with_array_of_length_0(): + BInt = new_primitive_type("int") + BArray0 = new_array_type(new_pointer_type(BInt), 0) + BStruct = new_struct_type("struct foo") + BStructP = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BArray0)]) + BFunc = new_function_type((BStruct,), BInt, False) + py.test.raises(NotImplementedError, cast(BFunc, 123), cast(BStructP, 123)) + BFunc2 = new_function_type((BInt,), BStruct, False) + py.test.raises(NotImplementedError, cast(BFunc2, 123), 123) + +def test_call_function_9(): + BInt = new_primitive_type("int") + BFunc9 = new_function_type((BInt,), BInt, True) # vararg + f = cast(BFunc9, _testfunc(9)) + assert f(0) == 0 + assert f(1, cast(BInt, 42)) == 42 + assert f(2, cast(BInt, 40), cast(BInt, 2)) == 42 + py.test.raises(TypeError, f, 1, 42) + py.test.raises(TypeError, f, 2, None) + # promotion of chars and shorts to ints + BSChar = new_primitive_type("signed char") + BUChar = new_primitive_type("unsigned char") + BSShort = new_primitive_type("short") + assert f(3, cast(BSChar, -3), cast(BUChar, 200), cast(BSShort, -5)) == 192 + +def test_call_function_24(): + BFloat = new_primitive_type("float") + BFloatComplex = new_primitive_type("float _Complex") + BFunc3 = new_function_type((BFloat, BFloat), BFloatComplex, False) + if 0: # libffi returning nonsense silently, so logic disabled for now + f = cast(BFunc3, _testfunc(24)) + result = f(1.25, 5.1) + assert type(result) == complex + assert result.real == 1.25 # exact + assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact + else: + f = cast(BFunc3, _testfunc(9)) + py.test.raises(NotImplementedError, f, 12.3, 34.5) + +def test_call_function_25(): + BDouble = new_primitive_type("double") + BDoubleComplex = new_primitive_type("double _Complex") + BFunc3 = new_function_type((BDouble, BDouble), BDoubleComplex, False) + if 0: # libffi returning nonsense silently, so logic disabled for now + f = cast(BFunc3, _testfunc(25)) + result = f(1.25, 5.1) + assert type(result) == complex + assert result.real == 1.25 # exact + assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-10) # inexact + else: + f = cast(BFunc3, _testfunc(9)) + py.test.raises(NotImplementedError, f, 12.3, 34.5) + +def test_cannot_call_with_a_autocompleted_struct(): + BSChar = new_primitive_type("signed char") + BDouble = new_primitive_type("double") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('c', BDouble, -1, 8), + ('a', BSChar, -1, 2), + ('b', BSChar, -1, 0)]) + 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; 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(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + x = newp(BCharA, 42) + assert len(x) == 42 + x = newp(BCharA, b"foobar") + assert len(x) == 7 + +def test_load_and_call_function(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BLong = new_primitive_type("long") + BFunc = new_function_type((BCharP,), BLong, False) + ll = find_and_load_library('c') + strlen = ll.load_function(BFunc, "strlen") + input = newp(new_array_type(BCharP, None), b"foobar") + assert strlen(input) == 6 + # + assert strlen(b"foobarbaz") == 9 + # + BVoidP = new_pointer_type(new_void_type()) + strlenaddr = ll.load_function(BVoidP, "strlen") + assert strlenaddr == cast(BVoidP, strlen) + +def test_read_variable(): + ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard + ## https://bugs.pypy.org/issue1643 + if not sys.platform.startswith("linux"): + py.test.skip("untested") + BVoidP = new_pointer_type(new_void_type()) + ll = find_and_load_library('c') + stderr = ll.read_variable(BVoidP, "stderr") + assert stderr == cast(BVoidP, _testfunc(8)) + # + ll.close_lib() + py.test.raises(ValueError, ll.read_variable, BVoidP, "stderr") + +def test_read_variable_as_unknown_length_array(): + ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard + ## https://bugs.pypy.org/issue1643 + if not sys.platform.startswith("linux"): + py.test.skip("untested") + BCharP = new_pointer_type(new_primitive_type("char")) + BArray = new_array_type(BCharP, None) + ll = find_and_load_library('c') + stderr = ll.read_variable(BArray, "stderr") + assert repr(stderr).startswith(": +Traceback (most recent call last): + File "$", line $, in Zcb1 + $ + File "$", line $, in check_value + $ +ValueError: 42 +""") + sys.stderr = cStringIO.StringIO() + bigvalue = 20000 + assert f(bigvalue) == -42 + assert matches(sys.stderr.getvalue(), """\ +From cffi callback : +Trying to convert the result back to C: +OverflowError: integer 60000 does not fit 'short' +""") + sys.stderr = cStringIO.StringIO() + bigvalue = 20000 + assert len(seen) == 0 + assert ff(bigvalue) == -42 + assert sys.stderr.getvalue() == "" + assert len(seen) == 1 + exc, val, tb = seen[0] + assert exc is OverflowError + assert str(val) == "integer 60000 does not fit 'short'" + # + sys.stderr = cStringIO.StringIO() + bigvalue = 20000 + del seen[:] + oops_result = 81 + assert ff(bigvalue) == 81 + oops_result = None + assert sys.stderr.getvalue() == "" + assert len(seen) == 1 + exc, val, tb = seen[0] + assert exc is OverflowError + assert str(val) == "integer 60000 does not fit 'short'" + # + sys.stderr = cStringIO.StringIO() + bigvalue = 20000 + del seen[:] + oops_result = "xy" # not None and not an int! + assert ff(bigvalue) == -42 + oops_result = None + assert matches(sys.stderr.getvalue(), """\ +From cffi callback : +Trying to convert the result back to C: +OverflowError: integer 60000 does not fit 'short' + +During the call to 'onerror', another exception occurred: + +TypeError: $integer$ +""") + # + sys.stderr = cStringIO.StringIO() + seen = "not a list" # this makes the oops() function crash + assert ff(bigvalue) == -42 + assert matches(sys.stderr.getvalue(), """\ +From cffi callback : +Trying to convert the result back to C: +OverflowError: integer 60000 does not fit 'short' + +During the call to 'onerror', another exception occurred: + +Traceback (most recent call last): + File "$", line $, in oops + $ +AttributeError: 'str' object has no attribute 'append' +""") + finally: + sys.stderr = orig_stderr + linecache.getline = orig_getline + +def test_callback_return_type(): + for rettype in ["signed char", "short", "int", "long", "long long", + "unsigned char", "unsigned short", "unsigned int", + "unsigned long", "unsigned long long"]: + BRet = new_primitive_type(rettype) + def cb(n): + return n + 1 + BFunc = new_function_type((BRet,), BRet) + f = callback(BFunc, cb, 42) + assert f(41) == 42 + if rettype.startswith("unsigned "): + min = 0 + max = (1 << (8*sizeof(BRet))) - 1 + else: + min = -(1 << (8*sizeof(BRet)-1)) + max = (1 << (8*sizeof(BRet)-1)) - 1 + assert f(min) == min + 1 + assert f(max - 1) == max + assert f(max) == 42 + +def test_a_lot_of_callbacks(): + BIGNUM = 10000 + if 'PY_DOT_PY' in globals(): BIGNUM = 100 # tests on py.py + # + BInt = new_primitive_type("int") + BFunc = new_function_type((BInt,), BInt, False) + def make_callback(m): + def cb(n): + return n + m + return callback(BFunc, cb, 42) # 'cb' and 'BFunc' go out of scope + # + flist = [make_callback(i) for i in range(BIGNUM)] + for i, f in enumerate(flist): + assert f(-142) == -142 + i + +def test_callback_receiving_tiny_struct(): + BSChar = new_primitive_type("signed char") + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BSChar, -1), + ('b', BSChar, -1)]) + def cb(s): + return s.a + 10 * s.b + BFunc = new_function_type((BStruct,), BInt) + f = callback(BFunc, cb) + p = newp(BStructPtr, [-2, -4]) + n = f(p[0]) + assert n == -42 + +def test_callback_returning_tiny_struct(): + BSChar = new_primitive_type("signed char") + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BSChar, -1), + ('b', BSChar, -1)]) + def cb(n): + return newp(BStructPtr, [-n, -3*n])[0] + BFunc = new_function_type((BInt,), BStruct) + f = callback(BFunc, cb) + s = f(10) + assert typeof(s) is BStruct + assert repr(s) == "" + assert s.a == -10 + assert s.b == -30 + +def test_callback_receiving_struct(): + BSChar = new_primitive_type("signed char") + BInt = new_primitive_type("int") + BDouble = new_primitive_type("double") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BSChar, -1), + ('b', BDouble, -1)]) + def cb(s): + return s.a + int(s.b) + BFunc = new_function_type((BStruct,), BInt) + f = callback(BFunc, cb) + p = newp(BStructPtr, [-2, 44.444]) + n = f(p[0]) + assert n == 42 + +def test_callback_returning_struct(): + BSChar = new_primitive_type("signed char") + BInt = new_primitive_type("int") + BDouble = new_primitive_type("double") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BSChar, -1), + ('b', BDouble, -1)]) + def cb(n): + return newp(BStructPtr, [-n, 1E-42])[0] + BFunc = new_function_type((BInt,), BStruct) + f = callback(BFunc, cb) + s = f(10) + assert typeof(s) is BStruct + assert repr(s) in ["", + ""] + assert s.a == -10 + assert s.b == 1E-42 + +def test_callback_receiving_big_struct(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BInt, -1), + ('b', BInt, -1), + ('c', BInt, -1), + ('d', BInt, -1), + ('e', BInt, -1), + ('f', BInt, -1), + ('g', BInt, -1), + ('h', BInt, -1), + ('i', BInt, -1), + ('j', BInt, -1)]) + def cb(s): + for i, name in enumerate("abcdefghij"): + assert getattr(s, name) == 13 - i + return 42 + BFunc = new_function_type((BStruct,), BInt) + f = callback(BFunc, cb) + p = newp(BStructPtr, list(range(13, 3, -1))) + n = f(p[0]) + assert n == 42 + +def test_callback_returning_big_struct(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a', BInt, -1), + ('b', BInt, -1), + ('c', BInt, -1), + ('d', BInt, -1), + ('e', BInt, -1), + ('f', BInt, -1), + ('g', BInt, -1), + ('h', BInt, -1), + ('i', BInt, -1), + ('j', BInt, -1)]) + def cb(): + return newp(BStructPtr, list(range(13, 3, -1)))[0] + BFunc = new_function_type((), BStruct) + f = callback(BFunc, cb) + s = f() + assert typeof(s) is BStruct + assert repr(s) in ["", + ""] + for i, name in enumerate("abcdefghij"): + assert getattr(s, name) == 13 - i + +def test_callback_returning_void(): + BVoid = new_void_type() + BFunc = new_function_type((), BVoid, False) + def cb(): + seen.append(42) + f = callback(BFunc, cb) + seen = [] + f() + assert seen == [42] + py.test.raises(TypeError, callback, BFunc, cb, -42) + +def test_enum_type(): + BUInt = new_primitive_type("unsigned int") + BEnum = new_enum_type("foo", (), (), BUInt) + assert repr(BEnum) == "" + assert BEnum.kind == "enum" + assert BEnum.cname == "foo" + assert BEnum.elements == {} + # + BInt = new_primitive_type("int") + BEnum = new_enum_type("enum foo", ('def', 'c', 'ab'), (0, 1, -20), BInt) + assert BEnum.kind == "enum" + assert BEnum.cname == "enum foo" + assert BEnum.elements == {-20: 'ab', 0: 'def', 1: 'c'} + # 'elements' is not the real dict, but merely a copy + BEnum.elements[2] = '??' + assert BEnum.elements == {-20: 'ab', 0: 'def', 1: 'c'} + # + BEnum = new_enum_type("enum bar", ('ab', 'cd'), (5, 5), BUInt) + assert BEnum.elements == {5: 'ab'} + assert BEnum.relements == {'ab': 5, 'cd': 5} + +def test_cast_to_enum(): + BInt = new_primitive_type("int") + BEnum = new_enum_type("enum foo", ('def', 'c', 'ab'), (0, 1, -20), BInt) + assert sizeof(BEnum) == sizeof(BInt) + e = cast(BEnum, 0) + assert repr(e) == "" + assert repr(cast(BEnum, -42)) == "" + assert repr(cast(BEnum, -20)) == "" + assert string(e) == 'def' + assert string(cast(BEnum, -20)) == 'ab' + assert int(cast(BEnum, 1)) == 1 + assert int(cast(BEnum, 0)) == 0 + assert int(cast(BEnum, -242 + 2**128)) == -242 + assert string(cast(BEnum, -242 + 2**128)) == '-242' + # + BUInt = new_primitive_type("unsigned int") + BEnum = new_enum_type("enum bar", ('def', 'c', 'ab'), (0, 1, 20), BUInt) + e = cast(BEnum, -1) + assert repr(e) == "" # unsigned int + # + BLong = new_primitive_type("long") + BEnum = new_enum_type("enum baz", (), (), BLong) + assert sizeof(BEnum) == sizeof(BLong) + e = cast(BEnum, -1) + assert repr(e) == "" + +def test_enum_with_non_injective_mapping(): + BInt = new_primitive_type("int") + BEnum = new_enum_type("enum foo", ('ab', 'cd'), (7, 7), BInt) + e = cast(BEnum, 7) + assert repr(e) == "" + assert string(e) == 'ab' + +def test_enum_in_struct(): + BInt = new_primitive_type("int") + BEnum = new_enum_type("enum foo", ('def', 'c', 'ab'), (0, 1, -20), BInt) + BStruct = new_struct_type("struct bar") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BEnum, -1)]) + p = newp(BStructPtr, [-20]) + assert p.a1 == -20 + p = newp(BStructPtr, [12]) + assert p.a1 == 12 + e = py.test.raises(TypeError, newp, BStructPtr, [None]) + msg = str(e.value) + assert ("an integer is required" in msg or # CPython + "unsupported operand type for int(): 'NoneType'" in msg or # old PyPys + "expected integer, got NoneType object" in msg) # newer PyPys + py.test.raises(TypeError, 'p.a1 = "def"') + if sys.version_info < (3,): + BEnum2 = new_enum_type(unicode("foo"), (unicode('abc'),), (5,), BInt) + assert string(cast(BEnum2, 5)) == 'abc' + assert type(string(cast(BEnum2, 5))) is str + +def test_enum_overflow(): + max_uint = 2 ** (size_of_int()*8) - 1 + max_int = max_uint // 2 + max_ulong = 2 ** (size_of_long()*8) - 1 + max_long = max_ulong // 2 + for BPrimitive in [new_primitive_type("int"), + new_primitive_type("unsigned int"), + new_primitive_type("long"), + new_primitive_type("unsigned long")]: + for x in [max_uint, max_int, max_ulong, max_long]: + for testcase in [x, x+1, -x-1, -x-2]: + if int(cast(BPrimitive, testcase)) == testcase: + # fits + BEnum = new_enum_type("foo", ("AA",), (testcase,), + BPrimitive) + assert int(cast(BEnum, testcase)) == testcase + else: + # overflows + py.test.raises(OverflowError, new_enum_type, + "foo", ("AA",), (testcase,), BPrimitive) + +def test_callback_returning_enum(): + BInt = new_primitive_type("int") + BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20), BInt) + def cb(n): + if n & 1: + return cast(BEnum, n) + else: + return n + BFunc = new_function_type((BInt,), BEnum) + f = callback(BFunc, cb) + assert f(0) == 0 + assert f(1) == 1 + assert f(-20) == -20 + assert f(20) == 20 + assert f(21) == 21 + +def test_callback_returning_enum_unsigned(): + BInt = new_primitive_type("int") + BUInt = new_primitive_type("unsigned int") + BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, 20), BUInt) + def cb(n): + if n & 1: + return cast(BEnum, n) + else: + return n + BFunc = new_function_type((BInt,), BEnum) + f = callback(BFunc, cb) + assert f(0) == 0 + assert f(1) == 1 + assert f(-21) == 2**32 - 21 + assert f(20) == 20 + assert f(21) == 21 + +def test_callback_returning_char(): + BInt = new_primitive_type("int") + BChar = new_primitive_type("char") + def cb(n): + return bytechr(n) + BFunc = new_function_type((BInt,), BChar) + f = callback(BFunc, cb) + assert f(0) == b'\x00' + assert f(255) == b'\xFF' + +def _hacked_pypy_uni4(): + pyuni4 = {1: True, 2: False}[len(u+'\U00012345')] + return 'PY_DOT_PY' in globals() and not pyuni4 + +def test_callback_returning_wchar_t(): + BInt = new_primitive_type("int") + BWChar = new_primitive_type("wchar_t") + def cb(n): + if n == -1: + return u+'\U00012345' + if n == -2: + raise ValueError + return unichr(n) + BFunc = new_function_type((BInt,), BWChar) + f = callback(BFunc, cb) + assert f(0) == unichr(0) + assert f(255) == unichr(255) + assert f(0x1234) == u+'\u1234' + if sizeof(BWChar) == 4 and not _hacked_pypy_uni4(): + assert f(-1) == u+'\U00012345' + assert f(-2) == u+'\x00' # and an exception printed to stderr + +def test_struct_with_bitfields(): + BLong = new_primitive_type("long") + BStruct = new_struct_type("struct foo") + LONGBITS = 8 * sizeof(BLong) + complete_struct_or_union(BStruct, [('a1', BLong, 1), + ('a2', BLong, 2), + ('a3', BLong, 3), + ('a4', BLong, LONGBITS - 5)]) + d = BStruct.fields + assert d[0][1].offset == d[1][1].offset == d[2][1].offset == 0 + assert d[3][1].offset == sizeof(BLong) + def f(m, r): + if sys.byteorder == 'little': + return r + else: + return LONGBITS - m - r + assert d[0][1].bitshift == f(1, 0) + assert d[0][1].bitsize == 1 + assert d[1][1].bitshift == f(2, 1) + assert d[1][1].bitsize == 2 + assert d[2][1].bitshift == f(3, 3) + assert d[2][1].bitsize == 3 + assert d[3][1].bitshift == f(LONGBITS - 5, 0) + assert d[3][1].bitsize == LONGBITS - 5 + assert sizeof(BStruct) == 2 * sizeof(BLong) + assert alignof(BStruct) == alignof(BLong) + +def test_bitfield_instance(): + BInt = new_primitive_type("int") + BUnsignedInt = new_primitive_type("unsigned int") + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, [('a1', BInt, 1), + ('a2', BUnsignedInt, 2), + ('a3', BInt, 3)]) + p = newp(new_pointer_type(BStruct), None) + p.a1 = -1 + assert p.a1 == -1 + p.a1 = 0 + py.test.raises(OverflowError, "p.a1 = 2") + assert p.a1 == 0 + # + p.a1 = -1 + p.a2 = 3 + p.a3 = -4 + py.test.raises(OverflowError, "p.a3 = 4") + e = py.test.raises(OverflowError, "p.a3 = -5") + assert str(e.value) == ("value -5 outside the range allowed by the " + "bit field width: -4 <= x <= 3") + assert p.a1 == -1 and p.a2 == 3 and p.a3 == -4 + # + # special case for convenience: "int x:1", while normally signed, + # allows also setting the value "1" (it still gets read back as -1) + p.a1 = 1 + assert p.a1 == -1 + e = py.test.raises(OverflowError, "p.a1 = -2") + assert str(e.value) == ("value -2 outside the range allowed by the " + "bit field width: -1 <= x <= 1") + +def test_bitfield_instance_init(): + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, [('a1', BInt, 1)]) + p = newp(new_pointer_type(BStruct), [-1]) + assert p.a1 == -1 + p = newp(new_pointer_type(BStruct), {'a1': -1}) + assert p.a1 == -1 + # + BUnion = new_union_type("union bar") + complete_struct_or_union(BUnion, [('a1', BInt, 1)]) + p = newp(new_pointer_type(BUnion), [-1]) + assert p.a1 == -1 + +def test_weakref(): + import _weakref + BInt = new_primitive_type("int") + BPtr = new_pointer_type(BInt) + rlist = [_weakref.ref(BInt), + _weakref.ref(newp(BPtr, 42)), + _weakref.ref(cast(BPtr, 42)), + _weakref.ref(cast(BInt, 42)), + _weakref.ref(buffer(newp(BPtr, 42))), + ] + for i in range(5): + import gc; gc.collect() + if [r() for r in rlist] == [None for r in rlist]: + break + +def test_no_inheritance(): + BInt = new_primitive_type("int") + try: + class foo(type(BInt)): pass + except TypeError: + pass + else: + raise AssertionError + x = cast(BInt, 42) + try: + class foo(type(x)): pass + except TypeError: + pass + else: + raise AssertionError + +def test_assign_string(): + BChar = new_primitive_type("char") + BArray1 = new_array_type(new_pointer_type(BChar), 5) + BArray2 = new_array_type(new_pointer_type(BArray1), 5) + a = newp(BArray2, [b"abc", b"de", b"ghij"]) + assert string(a[1]) == b"de" + assert string(a[2]) == b"ghij" + a[2] = b"." + assert string(a[2]) == b"." + a[2] = b"12345" + assert string(a[2]) == b"12345" + e = py.test.raises(IndexError, 'a[2] = b"123456"') + assert 'char[5]' in str(e.value) + assert 'got 6 characters' in str(e.value) + +def test_add_error(): + x = cast(new_primitive_type("int"), 42) + py.test.raises(TypeError, "x + 1") + py.test.raises(TypeError, "x - 1") + +def test_void_errors(): + py.test.raises(ValueError, alignof, new_void_type()) + py.test.raises(TypeError, newp, new_pointer_type(new_void_type()), None) + +def test_too_many_items(): + BChar = new_primitive_type("char") + BArray = new_array_type(new_pointer_type(BChar), 5) + py.test.raises(IndexError, newp, BArray, tuple(b'123456')) + py.test.raises(IndexError, newp, BArray, list(b'123456')) + py.test.raises(IndexError, newp, BArray, b'123456') + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, []) + py.test.raises(TypeError, newp, new_pointer_type(BStruct), b'') + py.test.raises(ValueError, newp, new_pointer_type(BStruct), [b'1']) + +def test_more_type_errors(): + BInt = new_primitive_type("int") + BChar = new_primitive_type("char") + BArray = new_array_type(new_pointer_type(BChar), 5) + py.test.raises(TypeError, newp, BArray, 12.34) + BArray = new_array_type(new_pointer_type(BInt), 5) + py.test.raises(TypeError, newp, BArray, 12.34) + BFloat = new_primitive_type("float") + py.test.raises(TypeError, cast, BFloat, newp(BArray, None)) + +def test_more_overflow_errors(): + BUInt = new_primitive_type("unsigned int") + py.test.raises(OverflowError, newp, new_pointer_type(BUInt), -1) + py.test.raises(OverflowError, newp, new_pointer_type(BUInt), 2**32) + +def test_newp_copying(): + """Test that we can do newp(, ) for most + types, including same-type arrays. + """ + BInt = new_primitive_type("int") + p = newp(new_pointer_type(BInt), cast(BInt, 42)) + assert p[0] == 42 + # + BUInt = new_primitive_type("unsigned int") + p = newp(new_pointer_type(BUInt), cast(BUInt, 42)) + assert p[0] == 42 + # + BChar = new_primitive_type("char") + p = newp(new_pointer_type(BChar), cast(BChar, '!')) + assert p[0] == b'!' + # + BFloat = new_primitive_type("float") + p = newp(new_pointer_type(BFloat), cast(BFloat, 12.25)) + assert p[0] == 12.25 + # + BStruct = new_struct_type("struct foo_s") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BInt, -1)]) + s1 = newp(BStructPtr, [42]) + p1 = newp(new_pointer_type(BStructPtr), s1) + assert p1[0] == s1 + # + BArray = new_array_type(new_pointer_type(BInt), None) + a1 = newp(BArray, [1, 2, 3, 4]) + py.test.raises(TypeError, newp, BArray, a1) + BArray6 = new_array_type(new_pointer_type(BInt), 6) + a1 = newp(BArray6, [10, 20, 30]) + a2 = newp(BArray6, a1) + assert list(a2) == [10, 20, 30, 0, 0, 0] + # + s1 = newp(BStructPtr, [42]) + s2 = newp(BStructPtr, s1[0]) + assert s2.a1 == 42 + # + BUnion = new_union_type("union foo_u") + BUnionPtr = new_pointer_type(BUnion) + complete_struct_or_union(BUnion, [('a1', BInt, -1)]) + u1 = newp(BUnionPtr, [42]) + u2 = newp(BUnionPtr, u1[0]) + assert u2.a1 == 42 + # + BFunc = new_function_type((BInt,), BUInt) + p1 = cast(BFunc, 42) + p2 = newp(new_pointer_type(BFunc), p1) + assert p2[0] == p1 + +def test_string(): + BChar = new_primitive_type("char") + assert string(cast(BChar, 42)) == b'*' + assert string(cast(BChar, 0)) == b'\x00' + BCharP = new_pointer_type(BChar) + BArray = new_array_type(BCharP, 10) + a = newp(BArray, b"hello") + assert len(a) == 10 + assert string(a) == b"hello" + p = a + 2 + assert string(p) == b"llo" + assert string(newp(new_array_type(BCharP, 4), b"abcd")) == b"abcd" + py.test.raises(RuntimeError, string, cast(BCharP, 0)) + assert string(a, 4) == b"hell" + assert string(a, 5) == b"hello" + assert string(a, 6) == b"hello" + +def test_string_byte(): + BByte = new_primitive_type("signed char") + assert string(cast(BByte, 42)) == b'*' + assert string(cast(BByte, 0)) == b'\x00' + BArray = new_array_type(new_pointer_type(BByte), None) + a = newp(BArray, [65, 66, 67]) + assert type(string(a)) is bytes and string(a) == b'ABC' + # + BByte = new_primitive_type("unsigned char") + assert string(cast(BByte, 42)) == b'*' + assert string(cast(BByte, 0)) == b'\x00' + BArray = new_array_type(new_pointer_type(BByte), None) + a = newp(BArray, [65, 66, 67]) + assert type(string(a)) is bytes and string(a) == b'ABC' + if 'PY_DOT_PY' not in globals() and sys.version_info < (3,): + assert string(a, 8).startswith(b'ABC') # may contain additional garbage + +def test_string_wchar(): + for typename in ["wchar_t", "char16_t", "char32_t"]: + _test_string_wchar_variant(typename) + +def _test_string_wchar_variant(typename): + BWChar = new_primitive_type(typename) + assert string(cast(BWChar, 42)) == u+'*' + assert string(cast(BWChar, 0x4253)) == u+'\u4253' + assert string(cast(BWChar, 0)) == u+'\x00' + BArray = new_array_type(new_pointer_type(BWChar), None) + a = newp(BArray, [u+'A', u+'B', u+'C']) + assert type(string(a)) is unicode and string(a) == u+'ABC' + if 'PY_DOT_PY' not in globals() and sys.version_info < (3,): + try: + # may contain additional garbage + assert string(a, 8).startswith(u+'ABC') + except ValueError: # garbage contains values > 0x10FFFF + assert sizeof(BWChar) == 4 + +def test_string_typeerror(): + BShort = new_primitive_type("short") + BArray = new_array_type(new_pointer_type(BShort), None) + a = newp(BArray, [65, 66, 67]) + py.test.raises(TypeError, string, a) + +def test_bug_convert_to_ptr(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BDouble = new_primitive_type("double") + x = cast(BDouble, 42) + py.test.raises(TypeError, newp, new_pointer_type(BCharP), x) + +def test_set_struct_fields(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharArray10 = new_array_type(BCharP, 10) + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BCharArray10, -1)]) + p = newp(BStructPtr, None) + assert string(p.a1) == b'' + p.a1 = b'foo' + assert string(p.a1) == b'foo' + assert list(p.a1) == [b'f', b'o', b'o'] + [b'\x00'] * 7 + p.a1 = [b'x', b'y'] + assert string(p.a1) == b'xyo' + +def test_invalid_function_result_types(): + BFunc = new_function_type((), new_void_type()) + BArray = new_array_type(new_pointer_type(BFunc), 5) # works + new_function_type((), BFunc) # works + new_function_type((), new_primitive_type("int")) + new_function_type((), new_pointer_type(BFunc)) + BUnion = new_union_type("union foo_u") + complete_struct_or_union(BUnion, []) + BFunc = new_function_type((), BUnion) + py.test.raises(NotImplementedError, cast(BFunc, 123)) + py.test.raises(TypeError, new_function_type, (), BArray) + +def test_struct_return_in_func(): + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BFloat = new_primitive_type("float") + BDouble = new_primitive_type("double") + BInt = new_primitive_type("int") + BStruct = new_struct_type("struct foo_s") + complete_struct_or_union(BStruct, [('a1', BChar, -1), + ('a2', BShort, -1)]) + BFunc10 = new_function_type((BInt,), BStruct) + f = cast(BFunc10, _testfunc(10)) + s = f(40) + assert repr(s) == "" + assert s.a1 == bytechr(40) + assert s.a2 == 40 * 40 + # + BStruct11 = new_struct_type("struct test11") + complete_struct_or_union(BStruct11, [('a1', BInt, -1), + ('a2', BInt, -1)]) + BFunc11 = new_function_type((BInt,), BStruct11) + f = cast(BFunc11, _testfunc(11)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40 + assert s.a2 == 40 * 40 + # + BStruct12 = new_struct_type("struct test12") + complete_struct_or_union(BStruct12, [('a1', BDouble, -1), + ]) + BFunc12 = new_function_type((BInt,), BStruct12) + f = cast(BFunc12, _testfunc(12)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40.0 + # + BStruct13 = new_struct_type("struct test13") + complete_struct_or_union(BStruct13, [('a1', BInt, -1), + ('a2', BInt, -1), + ('a3', BInt, -1)]) + BFunc13 = new_function_type((BInt,), BStruct13) + f = cast(BFunc13, _testfunc(13)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40 + assert s.a2 == 40 * 40 + assert s.a3 == 40 * 40 * 40 + # + BStruct14 = new_struct_type("struct test14") + complete_struct_or_union(BStruct14, [('a1', BFloat, -1), + ]) + BFunc14 = new_function_type((BInt,), BStruct14) + f = cast(BFunc14, _testfunc(14)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40.0 + # + BStruct15 = new_struct_type("struct test15") + complete_struct_or_union(BStruct15, [('a1', BFloat, -1), + ('a2', BInt, -1)]) + BFunc15 = new_function_type((BInt,), BStruct15) + f = cast(BFunc15, _testfunc(15)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40.0 + assert s.a2 == 40 * 40 + # + BStruct16 = new_struct_type("struct test16") + complete_struct_or_union(BStruct16, [('a1', BFloat, -1), + ('a2', BFloat, -1)]) + BFunc16 = new_function_type((BInt,), BStruct16) + f = cast(BFunc16, _testfunc(16)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40.0 + assert s.a2 == -40.0 + # + BStruct17 = new_struct_type("struct test17") + complete_struct_or_union(BStruct17, [('a1', BInt, -1), + ('a2', BFloat, -1)]) + BFunc17 = new_function_type((BInt,), BStruct17) + f = cast(BFunc17, _testfunc(17)) + s = f(40) + assert repr(s) == "" + assert s.a1 == 40 + assert s.a2 == 40.0 * 40.0 + # + BStruct17Ptr = new_pointer_type(BStruct17) + BFunc18 = new_function_type((BStruct17Ptr,), BInt) + f = cast(BFunc18, _testfunc(18)) + x = f([[40, 2.5]]) + assert x == 42 + x = f([{'a2': 43.1}]) + assert x == 43 + +def test_cast_with_functionptr(): + BFunc = new_function_type((), new_void_type()) + BFunc2 = new_function_type((), new_primitive_type("short")) + BCharP = new_pointer_type(new_primitive_type("char")) + BIntP = new_pointer_type(new_primitive_type("int")) + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BFunc, -1)]) + newp(BStructPtr, [cast(BFunc, 0)]) + newp(BStructPtr, [cast(BCharP, 0)]) + py.test.raises(TypeError, newp, BStructPtr, [cast(BIntP, 0)]) + py.test.raises(TypeError, newp, BStructPtr, [cast(BFunc2, 0)]) + +def test_wchar(): + _test_wchar_variant("wchar_t") + if sys.platform.startswith("linux"): + BWChar = new_primitive_type("wchar_t") + assert sizeof(BWChar) == 4 + # wchar_t is often signed on Linux, but not always (e.g. on ARM) + assert int(cast(BWChar, -1)) in (-1, 4294967295) + +def test_char16(): + BChar16 = new_primitive_type("char16_t") + assert sizeof(BChar16) == 2 + _test_wchar_variant("char16_t") + assert int(cast(BChar16, -1)) == 0xffff # always unsigned + +def test_char32(): + BChar32 = new_primitive_type("char32_t") + assert sizeof(BChar32) == 4 + _test_wchar_variant("char32_t") + assert int(cast(BChar32, -1)) == 0xffffffff # always unsigned + +def _test_wchar_variant(typename): + BWChar = new_primitive_type(typename) + BInt = new_primitive_type("int") + pyuni4 = {1: True, 2: False}[len(u+'\U00012345')] + wchar4 = {2: False, 4: True}[sizeof(BWChar)] + assert str(cast(BWChar, 0x45)) == "" % ( + typename, mandatory_u_prefix) + assert str(cast(BWChar, 0x1234)) == "" % ( + typename, mandatory_u_prefix) + if not _hacked_pypy_uni4(): + if wchar4: + x = cast(BWChar, 0x12345) + assert str(x) == "" % ( + typename, mandatory_u_prefix) + assert int(x) == 0x12345 + else: + x = cast(BWChar, 0x18345) + assert str(x) == "" % ( + typename, mandatory_u_prefix) + assert int(x) == 0x8345 + # + BWCharP = new_pointer_type(BWChar) + BStruct = new_struct_type("struct foo_s") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BWChar, -1), + ('a2', BWCharP, -1)]) + s = newp(BStructPtr) + s.a1 = u+'\x00' + assert s.a1 == u+'\x00' + py.test.raises(TypeError, "s.a1 = b'a'") + py.test.raises(TypeError, "s.a1 = bytechr(0xFF)") + s.a1 = u+'\u1234' + assert s.a1 == u+'\u1234' + if pyuni4: + if wchar4: + s.a1 = u+'\U00012345' + assert s.a1 == u+'\U00012345' + elif wchar4: + if not _hacked_pypy_uni4(): + s.a1 = cast(BWChar, 0x12345) + assert s.a1 == u+'\ud808\udf45' + s.a1 = u+'\ud807\udf44' + assert s.a1 == u+'\U00011f44' + else: + py.test.raises(TypeError, "s.a1 = u+'\U00012345'") + # + BWCharArray = new_array_type(BWCharP, None) + a = newp(BWCharArray, u+'hello \u1234 world') + assert len(a) == 14 # including the final null + assert string(a) == u+'hello \u1234 world' + a[13] = u+'!' + assert string(a) == u+'hello \u1234 world!' + assert str(a) == repr(a) + assert a[6] == u+'\u1234' + a[6] = u+'-' + assert string(a) == u+'hello - world!' + assert str(a) == repr(a) + # + if wchar4 and not _hacked_pypy_uni4(): + u1 = u+'\U00012345\U00012346\U00012347' + a = newp(BWCharArray, u1) + assert len(a) == 4 + assert string(a) == u1 + assert len(list(a)) == 4 + expected = [u+'\U00012345', u+'\U00012346', u+'\U00012347', unichr(0)] + assert list(a) == expected + got = [a[i] for i in range(4)] + assert got == expected + py.test.raises(IndexError, 'a[4]') + # + w = cast(BWChar, 'a') + assert repr(w) == "" % (typename, mandatory_u_prefix) + assert str(w) == repr(w) + assert string(w) == u+'a' + assert int(w) == ord('a') + w = cast(BWChar, 0x1234) + assert repr(w) == "" % (typename, mandatory_u_prefix) + assert str(w) == repr(w) + assert string(w) == u+'\u1234' + assert int(w) == 0x1234 + w = cast(BWChar, u+'\u8234') + assert repr(w) == "" % (typename, mandatory_u_prefix) + assert str(w) == repr(w) + assert string(w) == u+'\u8234' + assert int(w) == 0x8234 + w = cast(BInt, u+'\u1234') + assert repr(w) == "" + if wchar4 and not _hacked_pypy_uni4(): + w = cast(BWChar, u+'\U00012345') + assert repr(w) == "" % ( + typename, mandatory_u_prefix) + assert str(w) == repr(w) + assert string(w) == u+'\U00012345' + assert int(w) == 0x12345 + w = cast(BInt, u+'\U00012345') + assert repr(w) == "" + py.test.raises(TypeError, cast, BInt, u+'') + py.test.raises(TypeError, cast, BInt, u+'XX') + assert int(cast(BInt, u+'a')) == ord('a') + # + a = newp(BWCharArray, u+'hello - world') + p = cast(BWCharP, a) + assert string(p) == u+'hello - world' + p[6] = u+'\u2345' + assert string(p) == u+'hello \u2345 world' + # + s = newp(BStructPtr, [u+'\u1234', p]) + assert s.a1 == u+'\u1234' + assert s.a2 == p + assert str(s.a2) == repr(s.a2) + assert string(s.a2) == u+'hello \u2345 world' + # + q = cast(BWCharP, 0) + assert str(q) == repr(q) + py.test.raises(RuntimeError, string, q) + # + def cb(p): + assert repr(p).startswith("" + q = p[0] + assert repr(q) == "" + q.a1 = 123456 + assert p.a1 == 123456 + r = cast(BStructPtr, p) + assert repr(r[0]).startswith("" + assert q.a1 == 123456 + +def test_nokeepalive_struct(): + BStruct = new_struct_type("struct foo") + BStructPtr = new_pointer_type(BStruct) + BStructPtrPtr = new_pointer_type(BStructPtr) + complete_struct_or_union(BStruct, [('a1', new_primitive_type("int"), -1)]) + p = newp(BStructPtr) + pp = newp(BStructPtrPtr) + pp[0] = p + s = pp[0][0] + assert repr(s).startswith("" + assert sizeof(p) == 28 + # + BArray = new_array_type(new_pointer_type(BInt), 7) # int[7] + p = newp(BArray, None) + assert repr(p) == "" + assert sizeof(p) == 28 + +def test_cannot_dereference_void(): + BVoidP = new_pointer_type(new_void_type()) + p = cast(BVoidP, 123456) + py.test.raises(TypeError, "p[0]") + p = cast(BVoidP, 0) + py.test.raises((TypeError, RuntimeError), "p[0]") + +def test_iter(): + BInt = new_primitive_type("int") + BIntP = new_pointer_type(BInt) + BArray = new_array_type(BIntP, None) # int[] + p = newp(BArray, 7) + assert list(p) == list(iter(p)) == [0] * 7 + # + py.test.raises(TypeError, iter, cast(BInt, 5)) + py.test.raises(TypeError, iter, cast(BIntP, 123456)) + +def test_cmp(): + BInt = new_primitive_type("int") + BIntP = new_pointer_type(BInt) + BVoidP = new_pointer_type(new_void_type()) + p = newp(BIntP, 123) + q = cast(BInt, 124) + assert (p == q) is False + assert (p != q) is True + assert (q == p) is False + assert (q != p) is True + if strict_compare: + py.test.raises(TypeError, "p < q") + py.test.raises(TypeError, "p <= q") + py.test.raises(TypeError, "q < p") + py.test.raises(TypeError, "q <= p") + py.test.raises(TypeError, "p > q") + py.test.raises(TypeError, "p >= q") + r = cast(BVoidP, p) + assert (p < r) is False + assert (p <= r) is True + assert (p == r) is True + assert (p != r) is False + assert (p > r) is False + assert (p >= r) is True + s = newp(BIntP, 125) + assert (p == s) is False + assert (p != s) is True + assert (p < s) is (p <= s) is (s > p) is (s >= p) + assert (p > s) is (p >= s) is (s < p) is (s <= p) + assert (p < s) ^ (p > s) + +def test_buffer(): + try: + import __builtin__ + except ImportError: + import builtins as __builtin__ + BShort = new_primitive_type("short") + s = newp(new_pointer_type(BShort), 100) + assert sizeof(s) == size_of_ptr() + assert sizeof(BShort) == 2 + assert len(buffer(s)) == 2 + # + BChar = new_primitive_type("char") + BCharArray = new_array_type(new_pointer_type(BChar), None) + c = newp(BCharArray, b"hi there") + # + buf = buffer(c) + assert repr(buf).startswith('<_cffi_backend.buffer object at 0x') + assert bytes(buf) == b"hi there\x00" + assert type(buf) is buffer + if sys.version_info < (3,): + assert str(buf) == "hi there\x00" + assert unicode(buf) == u+"hi there\x00" + else: + assert str(buf) == repr(buf) + # --mb_length-- + assert len(buf) == len(b"hi there\x00") + # --mb_item-- + for i in range(-12, 12): + try: + expected = b"hi there\x00"[i] + except IndexError: + py.test.raises(IndexError, "buf[i]") + else: + assert buf[i] == bitem2bchr(expected) + # --mb_slice-- + assert buf[:] == b"hi there\x00" + for i in range(-12, 12): + assert buf[i:] == b"hi there\x00"[i:] + assert buf[:i] == b"hi there\x00"[:i] + for j in range(-12, 12): + assert buf[i:j] == b"hi there\x00"[i:j] + # --misc-- + assert list(buf) == list(map(bitem2bchr, b"hi there\x00")) + # --mb_as_buffer-- + if hasattr(__builtin__, 'buffer'): # Python <= 2.7 + py.test.raises(TypeError, __builtin__.buffer, c) + bf1 = __builtin__.buffer(buf) + assert len(bf1) == len(buf) and bf1[3] == "t" + if hasattr(__builtin__, 'memoryview'): # Python >= 2.7 + py.test.raises(TypeError, memoryview, c) + mv1 = memoryview(buf) + assert len(mv1) == len(buf) and mv1[3] in (b"t", ord(b"t")) + # --mb_ass_item-- + expected = list(map(bitem2bchr, b"hi there\x00")) + for i in range(-12, 12): + try: + expected[i] = bytechr(i & 0xff) + except IndexError: + py.test.raises(IndexError, "buf[i] = bytechr(i & 0xff)") + else: + buf[i] = bytechr(i & 0xff) + assert list(buf) == expected + # --mb_ass_slice-- + buf[:] = b"hi there\x00" + assert list(buf) == list(c) == list(map(bitem2bchr, b"hi there\x00")) + py.test.raises(ValueError, 'buf[:] = b"shorter"') + py.test.raises(ValueError, 'buf[:] = b"this is much too long!"') + buf[4:2] = b"" # no effect, but should work + assert buf[:] == b"hi there\x00" + buf[:2] = b"HI" + assert buf[:] == b"HI there\x00" + buf[:2] = b"hi" + expected = list(map(bitem2bchr, b"hi there\x00")) + x = 0 + for i in range(-12, 12): + for j in range(-12, 12): + start = i if i >= 0 else i + len(buf) + stop = j if j >= 0 else j + len(buf) + start = max(0, min(len(buf), start)) + stop = max(0, min(len(buf), stop)) + sample = bytechr(x & 0xff) * (stop - start) + x += 1 + buf[i:j] = sample + expected[i:j] = map(bitem2bchr, sample) + assert list(buf) == expected + +def test_getcname(): + BUChar = new_primitive_type("unsigned char") + BArray = new_array_type(new_pointer_type(BUChar), 123) + assert getcname(BArray, "<-->") == "unsigned char<-->[123]" + +def test_errno(): + BVoid = new_void_type() + BFunc5 = new_function_type((), BVoid) + f = cast(BFunc5, _testfunc(5)) + set_errno(50) + f() + assert get_errno() == 65 + f(); f() + assert get_errno() == 95 + +def test_errno_callback(): + if globals().get('PY_DOT_PY') == '2.5': + py.test.skip("cannot run this test on py.py with Python 2.5") + set_errno(95) + def cb(): + e = get_errno() + set_errno(e - 6) + BVoid = new_void_type() + BFunc5 = new_function_type((), BVoid) + f = callback(BFunc5, cb) + f() + assert get_errno() == 89 + f(); f() + assert get_errno() == 77 + +def test_cast_to_array(): + # not valid in C! extension to get a non-owning + BInt = new_primitive_type("int") + BIntP = new_pointer_type(BInt) + BArray = new_array_type(BIntP, 3) + x = cast(BArray, 0) + assert repr(x) == "" + +def test_cast_invalid(): + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, []) + p = cast(new_pointer_type(BStruct), 123456) + s = p[0] + py.test.raises(TypeError, cast, BStruct, s) + +def test_bug_float_convertion(): + BDouble = new_primitive_type("double") + BDoubleP = new_pointer_type(BDouble) + py.test.raises(TypeError, newp, BDoubleP, "foobar") + +def test_bug_delitem(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + x = newp(BCharP) + py.test.raises(TypeError, "del x[0]") + +def test_bug_delattr(): + BLong = new_primitive_type("long") + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, [('a1', BLong, -1)]) + x = newp(new_pointer_type(BStruct)) + py.test.raises(AttributeError, "del x.a1") + +def test_variable_length_struct(): + py.test.skip("later") + BLong = new_primitive_type("long") + BArray = new_array_type(new_pointer_type(BLong), None) + BStruct = new_struct_type("struct foo") + BStructP = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BLong, -1), + ('a2', BArray, -1)]) + assert sizeof(BStruct) == size_of_long() + assert alignof(BStruct) == alignof(BLong) + # + py.test.raises(TypeError, newp, BStructP, None) + x = newp(BStructP, 5) + assert sizeof(x) == 6 * size_of_long() + x[4] = 123 + assert x[4] == 123 + py.test.raises(IndexError, "x[5]") + assert len(x.a2) == 5 + # + py.test.raises(TypeError, newp, BStructP, [123]) + x = newp(BStructP, [123, 5]) + assert x.a1 == 123 + assert len(x.a2) == 5 + assert list(x.a2) == [0] * 5 + # + x = newp(BStructP, {'a2': 5}) + assert x.a1 == 0 + assert len(x.a2) == 5 + assert list(x.a2) == [0] * 5 + # + x = newp(BStructP, [123, (4, 5)]) + assert x.a1 == 123 + assert len(x.a2) == 2 + assert list(x.a2) == [4, 5] + # + x = newp(BStructP, {'a2': (4, 5)}) + assert x.a1 == 0 + assert len(x.a2) == 2 + assert list(x.a2) == [4, 5] + +def test_autocast_int(): + BInt = new_primitive_type("int") + BIntPtr = new_pointer_type(BInt) + BLongLong = new_primitive_type("long long") + BULongLong = new_primitive_type("unsigned long long") + BULongLongPtr = new_pointer_type(BULongLong) + x = newp(BIntPtr, cast(BInt, 42)) + assert x[0] == 42 + x = newp(BIntPtr, cast(BLongLong, 42)) + assert x[0] == 42 + x = newp(BIntPtr, cast(BULongLong, 42)) + assert x[0] == 42 + x = newp(BULongLongPtr, cast(BInt, 42)) + assert x[0] == 42 + py.test.raises(OverflowError, newp, BULongLongPtr, cast(BInt, -42)) + x = cast(BInt, cast(BInt, 42)) + assert int(x) == 42 + x = cast(BInt, cast(BLongLong, 42)) + assert int(x) == 42 + x = cast(BInt, cast(BULongLong, 42)) + assert int(x) == 42 + x = cast(BULongLong, cast(BInt, 42)) + assert int(x) == 42 + x = cast(BULongLong, cast(BInt, -42)) + assert int(x) == 2 ** 64 - 42 + x = cast(BIntPtr, cast(BInt, 42)) + assert int(cast(BInt, x)) == 42 + +def test_autocast_float(): + BFloat = new_primitive_type("float") + BDouble = new_primitive_type("float") + BFloatPtr = new_pointer_type(BFloat) + x = newp(BFloatPtr, cast(BDouble, 12.5)) + assert x[0] == 12.5 + x = cast(BFloat, cast(BDouble, 12.5)) + assert float(x) == 12.5 + +def test_longdouble(): + py_py = 'PY_DOT_PY' in globals() + BInt = new_primitive_type("int") + BLongDouble = new_primitive_type("long double") + BLongDoublePtr = new_pointer_type(BLongDouble) + BLongDoubleArray = new_array_type(BLongDoublePtr, None) + a = newp(BLongDoubleArray, 1) + x = a[0] + if not py_py: + assert repr(x).startswith(" sizeof(new_primitive_type("double")): + assert float(lstart) != start + assert repr(lstart).startswith("" + s = p[0] + assert repr(s) == "" + a = rawaddressof(BStructPtr, s, 0) + assert repr(a).startswith("= (3,): + try: + import posix, io + posix.fdopen = io.open + except ImportError: + pass # win32 + +def test_FILE(): + if sys.platform == "win32": + py.test.skip("testing FILE not implemented") + # + BFILE = new_struct_type("struct _IO_FILE") + BFILEP = new_pointer_type(BFILE) + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BInt = new_primitive_type("int") + BFunc = new_function_type((BCharP, BFILEP), BInt, False) + BFunc2 = new_function_type((BFILEP, BCharP), BInt, True) + ll = find_and_load_library('c') + fputs = ll.load_function(BFunc, "fputs") + fscanf = ll.load_function(BFunc2, "fscanf") + # + import posix + fdr, fdw = posix.pipe() + fr1 = posix.fdopen(fdr, 'rb', 256) + fw1 = posix.fdopen(fdw, 'wb', 256) + # + fw1.write(b"X") + res = fputs(b"hello world\n", fw1) + assert res >= 0 + fw1.flush() # should not be needed + # + p = newp(new_array_type(BCharP, 100), None) + res = fscanf(fr1, b"%s\n", p) + assert res == 1 + assert string(p) == b"Xhello" + fr1.close() + fw1.close() + +def test_FILE_only_for_FILE_arg(): + if sys.platform == "win32": + py.test.skip("testing FILE not implemented") + # + B_NOT_FILE = new_struct_type("struct NOT_FILE") + B_NOT_FILEP = new_pointer_type(B_NOT_FILE) + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BInt = new_primitive_type("int") + BFunc = new_function_type((BCharP, B_NOT_FILEP), BInt, False) + ll = find_and_load_library('c') + fputs = ll.load_function(BFunc, "fputs") + # + import posix + fdr, fdw = posix.pipe() + fr1 = posix.fdopen(fdr, 'r') + fw1 = posix.fdopen(fdw, 'w') + # + e = py.test.raises(TypeError, fputs, b"hello world\n", fw1) + assert str(e.value).startswith( + "initializer for ctype 'struct NOT_FILE *' must " + "be a cdata pointer, not ") + +def test_FILE_object(): + if sys.platform == "win32": + py.test.skip("testing FILE not implemented") + # + BFILE = new_struct_type("FILE") + BFILEP = new_pointer_type(BFILE) + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BInt = new_primitive_type("int") + BFunc = new_function_type((BCharP, BFILEP), BInt, False) + BFunc2 = new_function_type((BFILEP,), BInt, False) + ll = find_and_load_library('c') + fputs = ll.load_function(BFunc, "fputs") + fileno = ll.load_function(BFunc2, "fileno") + # + import posix + fdr, fdw = posix.pipe() + fw1 = posix.fdopen(fdw, 'wb', 256) + # + fw1p = cast(BFILEP, fw1) + fw1.write(b"X") + fw1.flush() + res = fputs(b"hello\n", fw1p) + assert res >= 0 + res = fileno(fw1p) + assert (res == fdw) == (sys.version_info < (3,)) + fw1.close() + # + data = posix.read(fdr, 256) + assert data == b"Xhello\n" + posix.close(fdr) + +def test_errno_saved(): + set_errno(42) + # a random function that will reset errno to 0 (at least on non-windows) + import os; os.stat('.') + # + res = get_errno() + assert res == 42 + +def test_GetLastError(): + if sys.platform != "win32": + py.test.skip("GetLastError(): only for Windows") + # + lib = find_and_load_library('KERNEL32.DLL') + BInt = new_primitive_type("int") + BVoid = new_void_type() + BFunc1 = new_function_type((BInt,), BVoid, False) + BFunc2 = new_function_type((), BInt, False) + SetLastError = lib.load_function(BFunc1, "SetLastError") + GetLastError = lib.load_function(BFunc2, "GetLastError") + # + SetLastError(42) + # a random function that will reset the real GetLastError() to 0 + import nt; nt.stat('.') + # + res = GetLastError() + assert res == 42 + # + SetLastError(2) + code, message = getwinerror() + assert code == 2 + assert message == "The system cannot find the file specified" + # + code, message = getwinerror(1155) + assert code == 1155 + assert message == ("No application is associated with the " + "specified file for this operation") + +def test_nonstandard_integer_types(): + for typename in ['int8_t', 'uint8_t', 'int16_t', 'uint16_t', 'int32_t', + 'uint32_t', 'int64_t', 'uint64_t', 'intptr_t', + 'uintptr_t', 'ptrdiff_t', 'size_t', 'ssize_t', + 'int_least8_t', 'uint_least8_t', + 'int_least16_t', 'uint_least16_t', + 'int_least32_t', 'uint_least32_t', + 'int_least64_t', 'uint_least64_t', + 'int_fast8_t', 'uint_fast8_t', + 'int_fast16_t', 'uint_fast16_t', + 'int_fast32_t', 'uint_fast32_t', + 'int_fast64_t', 'uint_fast64_t', + 'intmax_t', 'uintmax_t']: + new_primitive_type(typename) # works + +def test_cannot_convert_unicode_to_charp(): + BCharP = new_pointer_type(new_primitive_type("char")) + BCharArray = new_array_type(BCharP, None) + py.test.raises(TypeError, newp, BCharArray, u+'foobar') + +def test_buffer_keepalive(): + BCharP = new_pointer_type(new_primitive_type("char")) + BCharArray = new_array_type(BCharP, None) + buflist = [] + for i in range(20): + c = newp(BCharArray, str2bytes("hi there %d" % i)) + buflist.append(buffer(c)) + import gc; gc.collect() + for i in range(20): + buf = buflist[i] + assert buf[:] == str2bytes("hi there %d\x00" % i) + +def test_slice(): + BIntP = new_pointer_type(new_primitive_type("int")) + BIntArray = new_array_type(BIntP, None) + c = newp(BIntArray, 5) + assert len(c) == 5 + assert repr(c) == "" + d = c[1:4] + assert len(d) == 3 + assert repr(d) == "" + d[0] = 123 + d[2] = 456 + assert c[1] == 123 + assert c[3] == 456 + assert d[2] == 456 + py.test.raises(IndexError, "d[3]") + py.test.raises(IndexError, "d[-1]") + +def test_slice_ptr(): + BIntP = new_pointer_type(new_primitive_type("int")) + BIntArray = new_array_type(BIntP, None) + c = newp(BIntArray, 5) + d = (c+1)[0:2] + assert len(d) == 2 + assert repr(d) == "" + d[1] += 50 + assert c[2] == 50 + +def test_slice_array_checkbounds(): + BIntP = new_pointer_type(new_primitive_type("int")) + BIntArray = new_array_type(BIntP, None) + c = newp(BIntArray, 5) + c[0:5] + assert len(c[5:5]) == 0 + py.test.raises(IndexError, "c[-1:1]") + cp = c + 0 + cp[-1:1] + +def test_nonstandard_slice(): + BIntP = new_pointer_type(new_primitive_type("int")) + BIntArray = new_array_type(BIntP, None) + c = newp(BIntArray, 5) + e = py.test.raises(IndexError, "c[:5]") + assert str(e.value) == "slice start must be specified" + e = py.test.raises(IndexError, "c[4:]") + assert str(e.value) == "slice stop must be specified" + e = py.test.raises(IndexError, "c[1:2:3]") + assert str(e.value) == "slice with step not supported" + e = py.test.raises(IndexError, "c[1:2:1]") + assert str(e.value) == "slice with step not supported" + e = py.test.raises(IndexError, "c[4:2]") + assert str(e.value) == "slice start > stop" + e = py.test.raises(IndexError, "c[6:6]") + assert str(e.value) == "index too large (expected 6 <= 5)" + +def test_setslice(): + BIntP = new_pointer_type(new_primitive_type("int")) + BIntArray = new_array_type(BIntP, None) + c = newp(BIntArray, 5) + c[1:3] = [100, 200] + assert list(c) == [0, 100, 200, 0, 0] + cp = c + 3 + cp[-1:1] = [300, 400] + assert list(c) == [0, 100, 300, 400, 0] + cp[-1:1] = iter([500, 600]) + assert list(c) == [0, 100, 500, 600, 0] + py.test.raises(ValueError, "cp[-1:1] = [1000]") + assert list(c) == [0, 100, 1000, 600, 0] + py.test.raises(ValueError, "cp[-1:1] = (700, 800, 900)") + assert list(c) == [0, 100, 700, 800, 0] + +def test_setslice_array(): + BIntP = new_pointer_type(new_primitive_type("int")) + BIntArray = new_array_type(BIntP, None) + c = newp(BIntArray, 5) + d = newp(BIntArray, [10, 20, 30]) + c[1:4] = d + assert list(c) == [0, 10, 20, 30, 0] + # + BShortP = new_pointer_type(new_primitive_type("short")) + BShortArray = new_array_type(BShortP, None) + d = newp(BShortArray, [40, 50]) + c[1:3] = d + assert list(c) == [0, 40, 50, 30, 0] + +def test_cdata_name_module_doc(): + p = new_primitive_type("signed char") + x = cast(p, 17) + assert x.__module__ == '_cffi_backend' + assert x.__name__ == '' + assert hasattr(x, '__doc__') + +def test_different_types_of_ptr_equality(): + BVoidP = new_pointer_type(new_void_type()) + BIntP = new_pointer_type(new_primitive_type("int")) + x = cast(BVoidP, 12345) + assert x == cast(BIntP, 12345) + assert x != cast(BIntP, 12344) + assert hash(x) == hash(cast(BIntP, 12345)) + +def test_new_handle(): + import _weakref + BVoidP = new_pointer_type(new_void_type()) + BCharP = new_pointer_type(new_primitive_type("char")) + class mylist(list): + pass + o = mylist([2, 3, 4]) + x = newp_handle(BVoidP, o) + assert repr(x) == "" + assert x + assert from_handle(x) is o + assert from_handle(cast(BCharP, x)) is o + wr = _weakref.ref(o) + del o + import gc; gc.collect() + assert wr() is not None + assert from_handle(x) == list((2, 3, 4)) + assert from_handle(cast(BCharP, x)) == list((2, 3, 4)) + del x + for i in range(3): + if wr() is not None: + import gc; gc.collect() + assert wr() is None + py.test.raises(RuntimeError, from_handle, cast(BCharP, 0)) + +def test_new_handle_cycle(): + import _weakref + BVoidP = new_pointer_type(new_void_type()) + class A(object): + pass + o = A() + o.cycle = newp_handle(BVoidP, o) + wr = _weakref.ref(o) + del o + for i in range(3): + if wr() is not None: + import gc; gc.collect() + assert wr() is None + +def _test_bitfield_details(flag): + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BInt = new_primitive_type("int") + BUInt = new_primitive_type("unsigned int") + BStruct = new_struct_type("struct foo1") + complete_struct_or_union(BStruct, [('a', BChar, -1), + ('b1', BInt, 9), + ('b2', BUInt, 7), + ('c', BChar, -1)], -1, -1, -1, flag) + if not (flag & SF_MSVC_BITFIELDS): # gcc, any variant + assert typeoffsetof(BStruct, 'c') == (BChar, 3) + assert sizeof(BStruct) == 4 + else: # msvc + assert typeoffsetof(BStruct, 'c') == (BChar, 8) + assert sizeof(BStruct) == 12 + assert alignof(BStruct) == 4 + # + p = newp(new_pointer_type(BStruct), None) + p.a = b'A' + p.b1 = -201 + p.b2 = 99 + p.c = b'\x9D' + raw = buffer(p)[:] + if sys.byteorder == 'little': + if flag & SF_MSVC_BITFIELDS: + assert raw == b'A\x00\x00\x007\xC7\x00\x00\x9D\x00\x00\x00' + elif flag & SF_GCC_LITTLE_ENDIAN: + assert raw == b'A7\xC7\x9D' + elif flag & SF_GCC_BIG_ENDIAN: + assert raw == b'A\xE3\x9B\x9D' + else: + raise AssertionError("bad flag") + else: + if flag & SF_MSVC_BITFIELDS: + assert raw == b'A\x00\x00\x00\x00\x00\xC77\x9D\x00\x00\x00' + elif flag & SF_GCC_LITTLE_ENDIAN: + assert raw == b'A\xC77\x9D' + elif flag & SF_GCC_BIG_ENDIAN: + assert raw == b'A\x9B\xE3\x9D' + else: + raise AssertionError("bad flag") + # + BStruct = new_struct_type("struct foo2") + complete_struct_or_union(BStruct, [('a', BChar, -1), + ('', BShort, 9), + ('c', BChar, -1)], -1, -1, -1, flag) + assert typeoffsetof(BStruct, 'c') == (BChar, 4) + if flag & SF_MSVC_BITFIELDS: + assert sizeof(BStruct) == 6 + assert alignof(BStruct) == 2 + elif flag & SF_GCC_X86_BITFIELDS: + assert sizeof(BStruct) == 5 + assert alignof(BStruct) == 1 + elif flag & SF_GCC_ARM_BITFIELDS: + assert sizeof(BStruct) == 6 + assert alignof(BStruct) == 2 + else: + raise AssertionError("bad flag") + # + BStruct = new_struct_type("struct foo2") + complete_struct_or_union(BStruct, [('a', BChar, -1), + ('', BInt, 0), + ('', BInt, 0), + ('c', BChar, -1)], -1, -1, -1, flag) + if flag & SF_MSVC_BITFIELDS: + assert typeoffsetof(BStruct, 'c') == (BChar, 1) + assert sizeof(BStruct) == 2 + assert alignof(BStruct) == 1 + elif flag & SF_GCC_X86_BITFIELDS: + assert typeoffsetof(BStruct, 'c') == (BChar, 4) + assert sizeof(BStruct) == 5 + assert alignof(BStruct) == 1 + elif flag & SF_GCC_ARM_BITFIELDS: + assert typeoffsetof(BStruct, 'c') == (BChar, 4) + assert sizeof(BStruct) == 8 + assert alignof(BStruct) == 4 + else: + raise AssertionError("bad flag") + + +SF_MSVC_BITFIELDS = 0x01 +SF_GCC_ARM_BITFIELDS = 0x02 +SF_GCC_X86_BITFIELDS = 0x10 + +SF_GCC_BIG_ENDIAN = 0x04 +SF_GCC_LITTLE_ENDIAN = 0x40 + +SF_PACKED = 0x08 + +def test_bitfield_as_x86_gcc(): + _test_bitfield_details(flag=SF_GCC_X86_BITFIELDS|SF_GCC_LITTLE_ENDIAN) + +def test_bitfield_as_msvc(): + _test_bitfield_details(flag=SF_MSVC_BITFIELDS|SF_GCC_LITTLE_ENDIAN) + +def test_bitfield_as_arm_gcc(): + _test_bitfield_details(flag=SF_GCC_ARM_BITFIELDS|SF_GCC_LITTLE_ENDIAN) + +def test_bitfield_as_ppc_gcc(): + # PowerPC uses the same format as X86, but is big-endian + _test_bitfield_details(flag=SF_GCC_X86_BITFIELDS|SF_GCC_BIG_ENDIAN) + + +def test_struct_array_no_length(): + BInt = new_primitive_type("int") + BIntP = new_pointer_type(BInt) + BArray = new_array_type(BIntP, None) + BStruct = new_struct_type("foo") + py.test.raises(TypeError, complete_struct_or_union, + BStruct, [('x', BArray), + ('y', BInt)]) + # + BStruct = new_struct_type("foo") + complete_struct_or_union(BStruct, [('x', BInt), + ('y', BArray)]) + assert sizeof(BStruct) == size_of_int() + d = BStruct.fields + assert len(d) == 2 + assert d[0][0] == 'x' + assert d[0][1].type is BInt + assert d[0][1].offset == 0 + assert d[0][1].bitshift == -1 + assert d[0][1].bitsize == -1 + assert d[1][0] == 'y' + assert d[1][1].type is BArray + assert d[1][1].offset == size_of_int() + assert d[1][1].bitshift == -2 + assert d[1][1].bitsize == -1 + # + p = newp(new_pointer_type(BStruct)) + p.x = 42 + assert p.x == 42 + assert typeof(p.y) is BArray + assert len(p.y) == 0 + assert p.y == cast(BIntP, p) + 1 + # + p = newp(new_pointer_type(BStruct), [100]) + assert p.x == 100 + assert len(p.y) == 0 + # + # Tests for + # ffi.new("struct_with_var_array *", [field.., [the_array_items..]]) + # ffi.new("struct_with_var_array *", [field.., array_size]) + plist = [] + for i in range(20): + if i % 2 == 0: + p = newp(new_pointer_type(BStruct), [100, [200, i, 400]]) + else: + p = newp(new_pointer_type(BStruct), [100, 3]) + p.y[1] = i + p.y[0] = 200 + assert p.y[2] == 0 + p.y[2] = 400 + assert len(p.y) == 3 + assert len(p[0].y) == 3 + assert len(buffer(p)) == sizeof(BInt) * 4 + assert sizeof(p[0]) == sizeof(BInt) * 4 + plist.append(p) + for i in range(20): + p = plist[i] + assert p.x == 100 + assert p.y[0] == 200 + assert p.y[1] == i + assert p.y[2] == 400 + assert list(p.y) == [200, i, 400] + # + # the following assignment works, as it normally would, for any array field + p.y = [501, 601] + assert list(p.y) == [501, 601, 400] + p[0].y = [500, 600] + assert list(p[0].y) == [500, 600, 400] + assert repr(p) == "" % ( + sizeof(BStruct) + 3 * sizeof(BInt),) + assert repr(p[0]) == "" % ( + sizeof(BStruct) + 3 * sizeof(BInt),) + assert sizeof(p[0]) == sizeof(BStruct) + 3 * sizeof(BInt) + # + # from a non-owning pointer, we can't get the length + q = cast(new_pointer_type(BStruct), p) + assert q.y[0] == 500 + assert q[0].y[0] == 500 + py.test.raises(TypeError, len, q.y) + py.test.raises(TypeError, len, q[0].y) + assert typeof(q.y) is BIntP + assert typeof(q[0].y) is BIntP + assert sizeof(q[0]) == sizeof(BStruct) + # + # error cases + py.test.raises(IndexError, "p.y[4]") + py.test.raises(TypeError, "p.y = cast(BIntP, 0)") + py.test.raises(TypeError, "p.y = 15") + py.test.raises(TypeError, "p.y = None") + # + # accepting this may be specified by the C99 standard, + # or a GCC strangeness... + BStruct2 = new_struct_type("bar") + complete_struct_or_union(BStruct2, [('f', BStruct), + ('n', BInt)]) + p = newp(new_pointer_type(BStruct2), {'n': 42}) + assert p.n == 42 + # + # more error cases + py.test.raises(TypeError, newp, new_pointer_type(BStruct), [100, None]) + BArray4 = new_array_type(BIntP, 4) + BStruct4 = new_struct_type("test4") + complete_struct_or_union(BStruct4, [('a', BArray4)]) # not varsized + py.test.raises(TypeError, newp, new_pointer_type(BStruct4), [None]) + py.test.raises(TypeError, newp, new_pointer_type(BStruct4), [4]) + p = newp(new_pointer_type(BStruct4), [[10, 20, 30]]) + assert p.a[0] == 10 + assert p.a[1] == 20 + assert p.a[2] == 30 + assert p.a[3] == 0 + +def test_struct_array_no_length_explicit_position(): + BInt = new_primitive_type("int") + BIntP = new_pointer_type(BInt) + BArray = new_array_type(BIntP, None) + BStruct = new_struct_type("foo") + complete_struct_or_union(BStruct, [('x', BArray, -1, 0), # actually 3 items + ('y', BInt, -1, 12)]) + p = newp(new_pointer_type(BStruct), [[10, 20], 30]) + assert p.x[0] == 10 + assert p.x[1] == 20 + assert p.x[2] == 0 + assert p.y == 30 + p = newp(new_pointer_type(BStruct), {'x': [40], 'y': 50}) + assert p.x[0] == 40 + assert p.x[1] == 0 + assert p.x[2] == 0 + assert p.y == 50 + p = newp(new_pointer_type(BStruct), {'y': 60}) + assert p.x[0] == 0 + assert p.x[1] == 0 + assert p.x[2] == 0 + assert p.y == 60 + # + # This "should" work too, allocating a larger structure + # (a bit strange in this case, but useful in general) + plist = [] + for i in range(20): + p = newp(new_pointer_type(BStruct), [[10, 20, 30, 40, 50, 60, 70]]) + plist.append(p) + for i in range(20): + p = plist[i] + assert p.x[0] == 10 + assert p.x[1] == 20 + assert p.x[2] == 30 + assert p.x[3] == 40 == p.y + assert p.x[4] == 50 + assert p.x[5] == 60 + assert p.x[6] == 70 + +def test_struct_array_not_aligned(): + # struct a { int x; char y; char z[]; }; + # ends up of size 8, but 'z' is at offset 5 + BChar = new_primitive_type("char") + BInt = new_primitive_type("int") + BCharP = new_pointer_type(BChar) + BArray = new_array_type(BCharP, None) + BStruct = new_struct_type("foo") + complete_struct_or_union(BStruct, [('x', BInt), + ('y', BChar), + ('z', BArray)]) + assert sizeof(BStruct) == 2 * size_of_int() + def offsetof(BType, fieldname): + return typeoffsetof(BType, fieldname)[1] + base = offsetof(BStruct, 'z') + assert base == size_of_int() + 1 + # + p = newp(new_pointer_type(BStruct), {'z': 3}) + assert sizeof(p[0]) == base + 3 + q = newp(new_pointer_type(BStruct), {'z': size_of_int()}) + assert sizeof(q) == size_of_ptr() + assert sizeof(q[0]) == base + size_of_int() + assert len(p.z) == 3 + assert len(p[0].z) == 3 + assert len(q.z) == size_of_int() + assert len(q[0].z) == size_of_int() + +def test_ass_slice(): + BChar = new_primitive_type("char") + BArray = new_array_type(new_pointer_type(BChar), None) + p = newp(BArray, b"foobar") + p[2:5] = [b"*", b"Z", b"T"] + p[1:3] = b"XY" + assert list(p) == [b"f", b"X", b"Y", b"Z", b"T", b"r", b"\x00"] + py.test.raises(TypeError, "p[1:5] = u+'XYZT'") + py.test.raises(TypeError, "p[1:5] = [1, 2, 3, 4]") + # + for typename in ["wchar_t", "char16_t", "char32_t"]: + BUniChar = new_primitive_type(typename) + BArray = new_array_type(new_pointer_type(BUniChar), None) + p = newp(BArray, u+"foobar") + p[2:5] = [u+"*", u+"Z", u+"T"] + p[1:3] = u+"XY" + assert list(p) == [u+"f", u+"X", u+"Y", u+"Z", u+"T", u+"r", u+"\x00"] + py.test.raises(TypeError, "p[1:5] = b'XYZT'") + py.test.raises(TypeError, "p[1:5] = [1, 2, 3, 4]") + +def test_void_p_arithmetic(): + BVoid = new_void_type() + BInt = new_primitive_type("intptr_t") + p = cast(new_pointer_type(BVoid), 100000) + assert int(cast(BInt, p)) == 100000 + assert int(cast(BInt, p + 42)) == 100042 + assert int(cast(BInt, p - (-42))) == 100042 + assert (p + 42) - p == 42 + q = cast(new_pointer_type(new_primitive_type("char")), 100000) + py.test.raises(TypeError, "p - q") + py.test.raises(TypeError, "q - p") + py.test.raises(TypeError, "p + cast(new_primitive_type('int'), 42)") + py.test.raises(TypeError, "p - cast(new_primitive_type('int'), 42)") + +def test_sizeof_sliced_array(): + BInt = new_primitive_type("int") + BArray = new_array_type(new_pointer_type(BInt), 10) + p = newp(BArray, None) + assert sizeof(p[2:9]) == 7 * sizeof(BInt) + +def test_packed(): + BLong = new_primitive_type("long") + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + for extra_args in [(SF_PACKED,), (0, 1)]: + BStruct = new_struct_type("struct foo") + complete_struct_or_union(BStruct, [('a1', BLong, -1), + ('a2', BChar, -1), + ('a3', BShort, -1)], + None, -1, -1, *extra_args) + d = BStruct.fields + assert len(d) == 3 + assert d[0][0] == 'a1' + assert d[0][1].type is BLong + assert d[0][1].offset == 0 + assert d[0][1].bitshift == -1 + assert d[0][1].bitsize == -1 + assert d[1][0] == 'a2' + assert d[1][1].type is BChar + assert d[1][1].offset == sizeof(BLong) + assert d[1][1].bitshift == -1 + assert d[1][1].bitsize == -1 + assert d[2][0] == 'a3' + assert d[2][1].type is BShort + assert d[2][1].offset == sizeof(BLong) + sizeof(BChar) + assert d[2][1].bitshift == -1 + assert d[2][1].bitsize == -1 + assert sizeof(BStruct) == sizeof(BLong) + sizeof(BChar) + sizeof(BShort) + assert alignof(BStruct) == 1 + # + BStruct2 = new_struct_type("struct foo") + complete_struct_or_union(BStruct2, [('b1', BChar, -1), + ('b2', BLong, -1)], + None, -1, -1, 0, 2) + d = BStruct2.fields + assert len(d) == 2 + assert d[0][0] == 'b1' + assert d[0][1].type is BChar + assert d[0][1].offset == 0 + assert d[0][1].bitshift == -1 + assert d[0][1].bitsize == -1 + assert d[1][0] == 'b2' + assert d[1][1].type is BLong + assert d[1][1].offset == 2 + assert d[1][1].bitshift == -1 + assert d[1][1].bitsize == -1 + assert sizeof(BStruct2) == 2 + sizeof(BLong) + assert alignof(BStruct2) == 2 + +def test_packed_with_bitfields(): + if sys.platform == "win32": + py.test.skip("testing gcc behavior") + BLong = new_primitive_type("long") + BChar = new_primitive_type("char") + BStruct = new_struct_type("struct foo") + py.test.raises(NotImplementedError, + complete_struct_or_union, + BStruct, [('a1', BLong, 30), + ('a2', BChar, 5)], + None, -1, -1, SF_PACKED) + +def test_from_buffer(): + import array + a = array.array('H', [10000, 20000, 30000]) + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + c = from_buffer(BCharA, a) + assert typeof(c) is BCharA + assert len(c) == 6 + assert repr(c) == "" + p = new_pointer_type(new_primitive_type("unsigned short")) + cast(p, c)[1] += 500 + assert list(a) == [10000, 20500, 30000] + +def test_from_buffer_not_str_unicode(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + p1 = from_buffer(BCharA, b"foo") + assert p1 == from_buffer(BCharA, b"foo") + import gc; gc.collect() + assert p1 == from_buffer(BCharA, b"foo") + py.test.raises(TypeError, from_buffer, BCharA, u+"foo") + try: + from __builtin__ import buffer + except ImportError: + pass + else: + # Python 2 only + contents = from_buffer(BCharA, buffer(b"foo")) + assert len(contents) == len(p1) + for i in range(len(contents)): + assert contents[i] == p1[i] + p4 = buffer(u+"foo") + contents = from_buffer(BCharA, buffer(u+"foo")) + assert len(contents) == len(p4) + for i in range(len(contents)): + assert contents[i] == p4[i] + try: + from __builtin__ import memoryview + except ImportError: + pass + else: + contents = from_buffer(BCharA, memoryview(b"foo")) + assert len(contents) == len(p1) + for i in range(len(contents)): + assert contents[i] == p1[i] + + +def test_from_buffer_bytearray(): + a = bytearray(b"xyz") + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + p = from_buffer(BCharA, a) + assert typeof(p) is BCharA + assert len(p) == 3 + assert repr(p) == "" + assert p[2] == b"z" + p[2] = b"." + assert a[2] == ord(".") + a[2] = ord("?") + assert p[2] == b"?" + +def test_from_buffer_more_cases(): + try: + from _cffi_backend import _testbuff + except ImportError: + py.test.skip("not for pypy") + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + # + def check1(bufobj, expected): + c = from_buffer(BCharA, bufobj) + assert typeof(c) is BCharA + if sys.version_info >= (3,): + expected = [bytes(c, "ascii") for c in expected] + assert list(c) == list(expected) + # + def check(methods, expected, expected_for_memoryview=None): + if sys.version_info >= (3,): + if methods <= 7: + return + if expected_for_memoryview is not None: + expected = expected_for_memoryview + class X(object): + pass + _testbuff(X, methods) + bufobj = X() + check1(bufobj, expected) + try: + from __builtin__ import buffer + bufobjb = buffer(bufobj) + except (TypeError, ImportError): + pass + else: + check1(bufobjb, expected) + try: + bufobjm = memoryview(bufobj) + except (TypeError, NameError): + pass + else: + check1(bufobjm, expected_for_memoryview or expected) + # + check(1, "RDB") + check(2, "WRB") + check(4, "CHB") + check(8, "GTB") + check(16, "ROB") + # + check(1 | 2, "RDB") + check(1 | 4, "RDB") + check(2 | 4, "CHB") + check(1 | 8, "RDB", "GTB") + check(1 | 16, "RDB", "ROB") + check(2 | 8, "WRB", "GTB") + check(2 | 16, "WRB", "ROB") + check(4 | 8, "CHB", "GTB") + check(4 | 16, "CHB", "ROB") + +def test_from_buffer_require_writable(): + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + p1 = from_buffer(BCharA, b"foo", False) + assert p1 == from_buffer(BCharA, b"foo", False) + py.test.raises((TypeError, BufferError), from_buffer, BCharA, b"foo", True) + ba = bytearray(b"foo") + p1 = from_buffer(BCharA, ba, True) + p1[0] = b"g" + assert ba == b"goo" + +def test_from_buffer_types(): + BInt = new_primitive_type("int") + BIntP = new_pointer_type(BInt) + BIntA = new_array_type(BIntP, None) + lst = [-12345678, 87654321, 489148] + bytestring = buffer(newp(BIntA, lst))[:] + b'XYZ' + # + p1 = from_buffer(BIntA, bytestring) # int[] + assert typeof(p1) is BIntA + assert len(p1) == 3 + assert p1[0] == lst[0] + assert p1[1] == lst[1] + assert p1[2] == lst[2] + py.test.raises(IndexError, "p1[3]") + py.test.raises(IndexError, "p1[-1]") + # + py.test.raises(TypeError, from_buffer, BInt, bytestring) + py.test.raises(TypeError, from_buffer, BIntP, bytestring) + # + BIntA2 = new_array_type(BIntP, 2) + p2 = from_buffer(BIntA2, bytestring) # int[2] + assert typeof(p2) is BIntA2 + assert len(p2) == 2 + assert p2[0] == lst[0] + assert p2[1] == lst[1] + py.test.raises(IndexError, "p2[2]") + py.test.raises(IndexError, "p2[-1]") + assert p2 == p1 + # + BIntA4 = new_array_type(BIntP, 4) # int[4]: too big + py.test.raises(ValueError, from_buffer, BIntA4, bytestring) + # + BStruct = new_struct_type("foo") + complete_struct_or_union(BStruct, [('a1', BInt, -1), + ('a2', BInt, -1)]) + BStructP = new_pointer_type(BStruct) + BStructA = new_array_type(BStructP, None) + p1 = from_buffer(BStructA, bytestring) # struct[] + assert len(p1) == 1 + assert typeof(p1) is BStructA + assert p1[0].a1 == lst[0] + assert p1[0].a2 == lst[1] + py.test.raises(IndexError, "p1[1]") + # + BEmptyStruct = new_struct_type("empty") + complete_struct_or_union(BEmptyStruct, [], Ellipsis, 0) + assert sizeof(BEmptyStruct) == 0 + BEmptyStructP = new_pointer_type(BEmptyStruct) + BEmptyStructA = new_array_type(BEmptyStructP, None) + py.test.raises(ZeroDivisionError, from_buffer, # empty[] + BEmptyStructA, bytestring) + # + BEmptyStructA5 = new_array_type(BEmptyStructP, 5) + p1 = from_buffer(BEmptyStructA5, bytestring) # struct empty[5] + assert typeof(p1) is BEmptyStructA5 + assert len(p1) == 5 + assert cast(BIntP, p1) == from_buffer(BIntA, bytestring) + +def test_memmove(): + Short = new_primitive_type("short") + ShortA = new_array_type(new_pointer_type(Short), None) + Char = new_primitive_type("char") + CharA = new_array_type(new_pointer_type(Char), None) + p = newp(ShortA, [-1234, -2345, -3456, -4567, -5678]) + memmove(p, p + 1, 4) + assert list(p) == [-2345, -3456, -3456, -4567, -5678] + p[2] = 999 + memmove(p + 2, p, 6) + assert list(p) == [-2345, -3456, -2345, -3456, 999] + memmove(p + 4, newp(CharA, b"\x71\x72"), 2) + if sys.byteorder == 'little': + assert list(p) == [-2345, -3456, -2345, -3456, 0x7271] + else: + assert list(p) == [-2345, -3456, -2345, -3456, 0x7172] + +def test_memmove_buffer(): + import array + Short = new_primitive_type("short") + ShortA = new_array_type(new_pointer_type(Short), None) + a = array.array('H', [10000, 20000, 30000]) + p = newp(ShortA, 5) + memmove(p, a, 6) + assert list(p) == [10000, 20000, 30000, 0, 0] + memmove(p + 1, a, 6) + assert list(p) == [10000, 10000, 20000, 30000, 0] + b = array.array('h', [-1000, -2000, -3000]) + memmove(b, a, 4) + assert b.tolist() == [10000, 20000, -3000] + assert a.tolist() == [10000, 20000, 30000] + p[0] = 999 + p[1] = 998 + p[2] = 997 + p[3] = 996 + p[4] = 995 + memmove(b, p, 2) + assert b.tolist() == [999, 20000, -3000] + memmove(b, p + 2, 4) + assert b.tolist() == [997, 996, -3000] + p[2] = -p[2] + p[3] = -p[3] + memmove(b, p + 2, 6) + assert b.tolist() == [-997, -996, 995] + +def test_memmove_readonly_readwrite(): + SignedChar = new_primitive_type("signed char") + SignedCharA = new_array_type(new_pointer_type(SignedChar), None) + p = newp(SignedCharA, 5) + memmove(p, b"abcde", 3) + assert list(p) == [ord("a"), ord("b"), ord("c"), 0, 0] + memmove(p, bytearray(b"ABCDE"), 2) + assert list(p) == [ord("A"), ord("B"), ord("c"), 0, 0] + py.test.raises((TypeError, BufferError), memmove, b"abcde", p, 3) + ba = bytearray(b"xxxxx") + memmove(dest=ba, src=p, n=3) + assert ba == bytearray(b"ABcxx") + memmove(ba, b"EFGH", 4) + assert ba == bytearray(b"EFGHx") + +def test_memmove_sign_check(): + SignedChar = new_primitive_type("signed char") + SignedCharA = new_array_type(new_pointer_type(SignedChar), None) + p = newp(SignedCharA, 5) + py.test.raises(ValueError, memmove, p, p + 1, -1) # not segfault + +def test_memmove_bad_cdata(): + BInt = new_primitive_type("int") + p = cast(BInt, 42) + py.test.raises(TypeError, memmove, p, bytearray(b'a'), 1) + py.test.raises(TypeError, memmove, bytearray(b'a'), p, 1) + +def test_dereference_null_ptr(): + BInt = new_primitive_type("int") + BIntPtr = new_pointer_type(BInt) + p = cast(BIntPtr, 0) + py.test.raises(RuntimeError, "p[0]") + py.test.raises(RuntimeError, "p[0] = 42") + py.test.raises(RuntimeError, "p[42]") + py.test.raises(RuntimeError, "p[42] = -1") + +def test_mixup(): + BStruct1 = new_struct_type("foo") + BStruct2 = new_struct_type("foo") # <= same name as BStruct1 + BStruct3 = new_struct_type("bar") + BStruct1Ptr = new_pointer_type(BStruct1) + BStruct2Ptr = new_pointer_type(BStruct2) + BStruct3Ptr = new_pointer_type(BStruct3) + BStruct1PtrPtr = new_pointer_type(BStruct1Ptr) + BStruct2PtrPtr = new_pointer_type(BStruct2Ptr) + BStruct3PtrPtr = new_pointer_type(BStruct3Ptr) + pp1 = newp(BStruct1PtrPtr) + pp2 = newp(BStruct2PtrPtr) + pp3 = newp(BStruct3PtrPtr) + pp1[0] = pp1[0] + e = py.test.raises(TypeError, "pp3[0] = pp1[0]") + assert str(e.value).startswith("initializer for ctype 'bar *' must be a ") + assert str(e.value).endswith(", not cdata 'foo *'") + e = py.test.raises(TypeError, "pp2[0] = pp1[0]") + assert str(e.value) == ("initializer for ctype 'foo *' appears indeed to " + "be 'foo *', but the types are different (check " + "that you are not e.g. mixing up different ffi " + "instances)") + +def test_stdcall_function_type(): + assert FFI_CDECL == FFI_DEFAULT_ABI + try: + stdcall = FFI_STDCALL + except NameError: + stdcall = FFI_DEFAULT_ABI + BInt = new_primitive_type("int") + BFunc = new_function_type((BInt, BInt), BInt, False, stdcall) + if stdcall != FFI_DEFAULT_ABI: + assert repr(BFunc) == "" + else: + assert repr(BFunc) == "" + +def test_get_common_types(): + d = {} + _get_common_types(d) + assert d['bool'] == '_Bool' + +def test_unpack(): + BChar = new_primitive_type("char") + BArray = new_array_type(new_pointer_type(BChar), 10) # char[10] + p = newp(BArray, b"abc\x00def") + p0 = p + assert unpack(p, 10) == b"abc\x00def\x00\x00\x00" + assert unpack(p+1, 5) == b"bc\x00de" + + for typename in ["wchar_t", "char16_t", "char32_t"]: + BWChar = new_primitive_type(typename) + BArray = new_array_type(new_pointer_type(BWChar), 10) # wchar_t[10] + p = newp(BArray, u"abc\x00def") + assert unpack(p, 10) == u"abc\x00def\x00\x00\x00" + + for typename, samples in [ + ("uint8_t", [0, 2**8-1]), + ("uint16_t", [0, 2**16-1]), + ("uint32_t", [0, 2**32-1]), + ("uint64_t", [0, 2**64-1]), + ("int8_t", [-2**7, 2**7-1]), + ("int16_t", [-2**15, 2**15-1]), + ("int32_t", [-2**31, 2**31-1]), + ("int64_t", [-2**63, 2**63-1]), + ("_Bool", [False, True]), + ("float", [0.0, 10.5]), + ("double", [12.34, 56.78]), + ]: + BItem = new_primitive_type(typename) + BArray = new_array_type(new_pointer_type(BItem), 10) + p = newp(BArray, samples) + result = unpack(p, len(samples)) + assert result == samples + for i in range(len(samples)): + assert result[i] == p[i] and type(result[i]) is type(p[i]) + assert (type(result[i]) is bool) == (type(samples[i]) is bool) + # + BInt = new_primitive_type("int") + py.test.raises(TypeError, unpack, p) + py.test.raises(TypeError, unpack, b"foobar", 6) + py.test.raises(TypeError, unpack, cast(BInt, 42), 1) + # + BPtr = new_pointer_type(BInt) + random_ptr = cast(BPtr, -424344) + other_ptr = cast(BPtr, 54321) + BArray = new_array_type(new_pointer_type(BPtr), None) + lst = unpack(newp(BArray, [random_ptr, other_ptr]), 2) + assert lst == [random_ptr, other_ptr] + # + BFunc = new_function_type((BInt, BInt), BInt, False) + BFuncPtr = new_pointer_type(BFunc) + lst = unpack(newp(new_array_type(BFuncPtr, None), 2), 2) + assert len(lst) == 2 + assert not lst[0] and not lst[1] + assert typeof(lst[0]) is BFunc + # + BStruct = new_struct_type("foo") + BStructPtr = new_pointer_type(BStruct) + e = py.test.raises(ValueError, unpack, cast(BStructPtr, 42), 5) + assert str(e.value) == "'foo *' points to items of unknown size" + complete_struct_or_union(BStruct, [('a1', BInt, -1), + ('a2', BInt, -1)]) + array_of_structs = newp(new_array_type(BStructPtr, None), [[4,5], [6,7]]) + lst = unpack(array_of_structs, 2) + assert typeof(lst[0]) is BStruct + assert lst[0].a1 == 4 and lst[1].a2 == 7 + # + py.test.raises(RuntimeError, unpack, cast(new_pointer_type(BChar), 0), 0) + py.test.raises(RuntimeError, unpack, cast(new_pointer_type(BChar), 0), 10) + # + py.test.raises(ValueError, unpack, p0, -1) + py.test.raises(ValueError, unpack, p, -1) + +def test_cdata_dir(): + BInt = new_primitive_type("int") + p = cast(BInt, 42) + check_dir(p, []) + p = newp(new_array_type(new_pointer_type(BInt), None), 5) + check_dir(p, []) + BStruct = new_struct_type("foo") + p = cast(new_pointer_type(BStruct), 0) + check_dir(p, []) # opaque + complete_struct_or_union(BStruct, [('a2', BInt, -1), + ('a1', BInt, -1)]) + check_dir(p, ['a1', 'a2']) # always sorted + p = newp(new_pointer_type(BStruct), None) + check_dir(p, ['a1', 'a2']) + check_dir(p[0], ['a1', 'a2']) + pp = newp(new_pointer_type(new_pointer_type(BStruct)), p) + check_dir(pp, []) + check_dir(pp[0], ['a1', 'a2']) + check_dir(pp[0][0], ['a1', 'a2']) + +def test_char_pointer_conversion(): + import warnings + assert __version__.startswith("1."), ( + "the warning will be an error if we ever release cffi 2.x") + BCharP = new_pointer_type(new_primitive_type("char")) + BIntP = new_pointer_type(new_primitive_type("int")) + BVoidP = new_pointer_type(new_void_type()) + BUCharP = new_pointer_type(new_primitive_type("unsigned char")) + z1 = cast(BCharP, 0) + z2 = cast(BIntP, 0) + z3 = cast(BVoidP, 0) + z4 = cast(BUCharP, 0) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + newp(new_pointer_type(BIntP), z1) # warn + assert len(w) == 1 + newp(new_pointer_type(BVoidP), z1) # fine + assert len(w) == 1 + newp(new_pointer_type(BCharP), z2) # warn + assert len(w) == 2 + newp(new_pointer_type(BVoidP), z2) # fine + assert len(w) == 2 + newp(new_pointer_type(BCharP), z3) # fine + assert len(w) == 2 + newp(new_pointer_type(BIntP), z3) # fine + assert len(w) == 2 + newp(new_pointer_type(BCharP), z4) # fine (ignore signedness here) + assert len(w) == 2 + newp(new_pointer_type(BUCharP), z1) # fine (ignore signedness here) + assert len(w) == 2 + newp(new_pointer_type(BUCharP), z3) # fine + assert len(w) == 2 + # check that the warnings are associated with lines in this file + assert w[1].lineno == w[0].lineno + 4 + +def test_primitive_comparison(): + def assert_eq(a, b): + assert (a == b) is True + assert (b == a) is True + assert (a != b) is False + assert (b != a) is False + assert (a < b) is False + assert (a <= b) is True + assert (a > b) is False + assert (a >= b) is True + assert (b < a) is False + assert (b <= a) is True + assert (b > a) is False + assert (b >= a) is True + assert hash(a) == hash(b) + def assert_lt(a, b, check_hash=True): + assert (a == b) is False + assert (b == a) is False + assert (a != b) is True + assert (b != a) is True + assert (a < b) is True + assert (a <= b) is True + assert (a > b) is False + assert (a >= b) is False + assert (b < a) is False + assert (b <= a) is False + assert (b > a) is True + assert (b >= a) is True + if check_hash: + assert hash(a) != hash(b) # (or at least, it is unlikely) + def assert_gt(a, b, check_hash=True): + assert_lt(b, a, check_hash) + def assert_ne(a, b): + assert (a == b) is False + assert (b == a) is False + assert (a != b) is True + assert (b != a) is True + if strict_compare: + py.test.raises(TypeError, "a < b") + py.test.raises(TypeError, "a <= b") + py.test.raises(TypeError, "a > b") + py.test.raises(TypeError, "a >= b") + py.test.raises(TypeError, "b < a") + py.test.raises(TypeError, "b <= a") + py.test.raises(TypeError, "b > a") + py.test.raises(TypeError, "b >= a") + elif a < b: + assert_lt(a, b) + else: + assert_lt(b, a) + assert_eq(5, 5) + assert_lt(3, 5) + assert_ne('5', 5) + # + t1 = new_primitive_type("char") + t2 = new_primitive_type("int") + t3 = new_primitive_type("unsigned char") + t4 = new_primitive_type("unsigned int") + t5 = new_primitive_type("float") + t6 = new_primitive_type("double") + assert_eq(cast(t1, 65), b'A') + assert_lt(cast(t1, 64), b'\x99') + assert_gt(cast(t1, 200), b'A') + assert_ne(cast(t1, 65), 65) + assert_eq(cast(t2, -25), -25) + assert_lt(cast(t2, -25), -24) + assert_gt(cast(t2, -25), -26) + assert_eq(cast(t3, 65), 65) + assert_ne(cast(t3, 65), b'A') + assert_ne(cast(t3, 65), cast(t1, 65)) + assert_gt(cast(t4, -1), -1, check_hash=False) + assert_gt(cast(t4, -1), cast(t2, -1), check_hash=False) + assert_gt(cast(t4, -1), 99999) + assert_eq(cast(t4, -1), 256 ** size_of_int() - 1) + assert_eq(cast(t5, 3.0), 3) + assert_eq(cast(t5, 3.5), 3.5) + assert_lt(cast(t5, 3.3), 3.3) # imperfect rounding + assert_eq(cast(t6, 3.3), 3.3) + assert_eq(cast(t5, 3.5), cast(t6, 3.5)) + assert_lt(cast(t5, 3.1), cast(t6, 3.1)) # imperfect rounding + assert_eq(cast(t5, 7.0), cast(t3, 7)) + assert_lt(cast(t5, 3.1), 3.101) + assert_gt(cast(t5, 3.1), 3) + +def test_explicit_release_new(): + # release() on a ffi.new() object has no effect on CPython, but + # really releases memory on PyPy. We can't test that effect + # though, because a released cdata is not marked. + BIntP = new_pointer_type(new_primitive_type("int")) + p = newp(BIntP) + p[0] = 42 + py.test.raises(IndexError, "p[1]") + release(p) + # here, reading p[0] might give garbage or segfault... + release(p) # no effect + # + BStruct = new_struct_type("struct foo") + BStructP = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('p', BIntP, -1)]) + pstruct = newp(BStructP) + assert pstruct.p == cast(BIntP, 0) + release(pstruct) + # here, reading pstruct.p might give garbage or segfault... + release(pstruct) # no effect + +def test_explicit_release_new_contextmgr(): + BIntP = new_pointer_type(new_primitive_type("int")) + with newp(BIntP) as p: + p[0] = 42 + assert p[0] == 42 + # here, reading p[0] might give garbage or segfault... + release(p) # no effect + +def test_explicit_release_badtype(): + BIntP = new_pointer_type(new_primitive_type("int")) + p = cast(BIntP, 12345) + py.test.raises(ValueError, release, p) + py.test.raises(ValueError, release, p) + BStruct = new_struct_type("struct foo") + BStructP = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('p', BIntP, -1)]) + pstruct = newp(BStructP) + py.test.raises(ValueError, release, pstruct[0]) + +def test_explicit_release_badtype_contextmgr(): + BIntP = new_pointer_type(new_primitive_type("int")) + p = cast(BIntP, 12345) + py.test.raises(ValueError, "with p: pass") + py.test.raises(ValueError, "with p: pass") + +def test_explicit_release_gc(): + BIntP = new_pointer_type(new_primitive_type("int")) + seen = [] + intp1 = newp(BIntP, 12345) + p1 = cast(BIntP, intp1) + p = gcp(p1, seen.append) + assert seen == [] + release(p) + assert seen == [p1] + assert p1[0] == 12345 + assert p[0] == 12345 # true so far, but might change to raise RuntimeError + release(p) # no effect + +def test_explicit_release_gc_contextmgr(): + BIntP = new_pointer_type(new_primitive_type("int")) + seen = [] + intp1 = newp(BIntP, 12345) + p1 = cast(BIntP, intp1) + p = gcp(p1, seen.append) + with p: + assert p[0] == 12345 + assert seen == [] + assert seen == [p1] + assert p1[0] == 12345 + assert p[0] == 12345 # true so far, but might change to raise RuntimeError + release(p) # no effect + +def test_explicit_release_from_buffer(): + a = bytearray(b"xyz") + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + p = from_buffer(BCharA, a) + assert p[2] == b"z" + release(p) + assert p[2] == b"z" # true so far, but might change to raise RuntimeError + release(p) # no effect + +def test_explicit_release_from_buffer_contextmgr(): + a = bytearray(b"xyz") + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + p = from_buffer(BCharA, a) + with p: + assert p[2] == b"z" + assert p[2] == b"z" # true so far, but might change to raise RuntimeError + release(p) # no effect + +def test_explicit_release_bytearray_on_cpython(): + if '__pypy__' in sys.builtin_module_names: + py.test.skip("pypy's bytearray are never locked") + a = bytearray(b"xyz") + BChar = new_primitive_type("char") + BCharP = new_pointer_type(BChar) + BCharA = new_array_type(BCharP, None) + a += b't' * 10 + p = from_buffer(BCharA, a) + py.test.raises(BufferError, "a += b'u' * 100") + release(p) + a += b'v' * 100 + release(p) # no effect + a += b'w' * 1000 + assert a == bytearray(b"xyz" + b't' * 10 + b'v' * 100 + b'w' * 1000) diff --git a/c/wchar_helper.h b/c/wchar_helper.h new file mode 100644 index 0000000..8e6ea58 --- /dev/null +++ b/c/wchar_helper.h @@ -0,0 +1,246 @@ +/* + * wchar_t helpers + */ + +typedef uint16_t cffi_char16_t; +typedef uint32_t cffi_char32_t; + + +#if Py_UNICODE_SIZE == 2 + +/* Before Python 2.7, PyUnicode_FromWideChar is not able to convert + wchar_t values greater than 65535 into two-unicode-characters surrogates. + But even the Python 2.7 version doesn't detect wchar_t values that are + out of range(1114112), and just returns nonsense. + + From cffi 1.11 we can't use it anyway, because we need a version + with char32_t input types. +*/ +static PyObject * +_my_PyUnicode_FromChar32(const cffi_char32_t *w, Py_ssize_t size) +{ + PyObject *unicode; + register Py_ssize_t i; + Py_ssize_t alloc; + const cffi_char32_t *orig_w; + + alloc = size; + orig_w = w; + for (i = size; i > 0; i--) { + if (*w > 0xFFFF) + alloc++; + w++; + } + w = orig_w; + unicode = PyUnicode_FromUnicode(NULL, alloc); + if (!unicode) + return NULL; + + /* Copy the wchar_t data into the new object */ + { + register Py_UNICODE *u; + u = PyUnicode_AS_UNICODE(unicode); + for (i = size; i > 0; i--) { + if (*w > 0xFFFF) { + cffi_char32_t ordinal; + if (*w > 0x10FFFF) { + PyErr_Format(PyExc_ValueError, + "char32_t out of range for " + "conversion to unicode: 0x%x", (int)*w); + Py_DECREF(unicode); + return NULL; + } + ordinal = *w++; + ordinal -= 0x10000; + *u++ = 0xD800 | (ordinal >> 10); + *u++ = 0xDC00 | (ordinal & 0x3FF); + } + else + *u++ = *w++; + } + } + return unicode; +} + +static PyObject * +_my_PyUnicode_FromChar16(const cffi_char16_t *w, Py_ssize_t size) +{ + return PyUnicode_FromUnicode((const Py_UNICODE *)w, size); +} + +#else /* Py_UNICODE_SIZE == 4 */ + +static PyObject * +_my_PyUnicode_FromChar32(const cffi_char32_t *w, Py_ssize_t size) +{ + return PyUnicode_FromUnicode((const Py_UNICODE *)w, size); +} + +static PyObject * +_my_PyUnicode_FromChar16(const cffi_char16_t *w, Py_ssize_t size) +{ + /* 'size' is the length of the 'w' array */ + PyObject *result = PyUnicode_FromUnicode(NULL, size); + + if (result != NULL) { + Py_UNICODE *u_base = PyUnicode_AS_UNICODE(result); + Py_UNICODE *u = u_base; + + if (size == 1) { /* performance only */ + *u = (cffi_char32_t)*w; + } + else { + while (size > 0) { + cffi_char32_t ch = *w++; + size--; + if (0xD800 <= ch && ch <= 0xDBFF && size > 0) { + cffi_char32_t ch2 = *w; + if (0xDC00 <= ch2 && ch2 <= 0xDFFF) { + ch = (((ch & 0x3FF)<<10) | (ch2 & 0x3FF)) + 0x10000; + w++; + size--; + } + } + *u++ = ch; + } + if (PyUnicode_Resize(&result, u - u_base) < 0) { + Py_DECREF(result); + return NULL; + } + } + } + return result; +} + +#endif + + +#define IS_SURROGATE(u) (0xD800 <= (u)[0] && (u)[0] <= 0xDBFF && \ + 0xDC00 <= (u)[1] && (u)[1] <= 0xDFFF) +#define AS_SURROGATE(u) (0x10000 + (((u)[0] - 0xD800) << 10) + \ + ((u)[1] - 0xDC00)) + +static int +_my_PyUnicode_AsSingleChar16(PyObject *unicode, cffi_char16_t *result, + char *err_got) +{ + Py_UNICODE *u = PyUnicode_AS_UNICODE(unicode); + if (PyUnicode_GET_SIZE(unicode) != 1) { + sprintf(err_got, "unicode string of length %zd", + PyUnicode_GET_SIZE(unicode)); + return -1; + } +#if Py_UNICODE_SIZE == 4 + if (((unsigned int)u[0]) > 0xFFFF) + { + sprintf(err_got, "larger-than-0xFFFF character"); + return -1; + } +#endif + *result = (cffi_char16_t)u[0]; + return 0; +} + +static int +_my_PyUnicode_AsSingleChar32(PyObject *unicode, cffi_char32_t *result, + char *err_got) +{ + Py_UNICODE *u = PyUnicode_AS_UNICODE(unicode); + if (PyUnicode_GET_SIZE(unicode) == 1) { + *result = (cffi_char32_t)u[0]; + return 0; + } +#if Py_UNICODE_SIZE == 2 + if (PyUnicode_GET_SIZE(unicode) == 2 && IS_SURROGATE(u)) { + *result = AS_SURROGATE(u); + return 0; + } +#endif + sprintf(err_got, "unicode string of length %zd", + PyUnicode_GET_SIZE(unicode)); + return -1; +} + +static Py_ssize_t _my_PyUnicode_SizeAsChar16(PyObject *unicode) +{ + Py_ssize_t length = PyUnicode_GET_SIZE(unicode); + Py_ssize_t result = length; + +#if Py_UNICODE_SIZE == 4 + Py_UNICODE *u = PyUnicode_AS_UNICODE(unicode); + Py_ssize_t i; + + for (i=0; i 0xFFFF) + result++; + } +#endif + return result; +} + +static Py_ssize_t _my_PyUnicode_SizeAsChar32(PyObject *unicode) +{ + Py_ssize_t length = PyUnicode_GET_SIZE(unicode); + Py_ssize_t result = length; + +#if Py_UNICODE_SIZE == 2 + Py_UNICODE *u = PyUnicode_AS_UNICODE(unicode); + Py_ssize_t i; + + for (i=0; i 0xFFFF) { + if (ordinal > 0x10FFFF) { + PyErr_Format(PyExc_ValueError, + "unicode character out of range for " + "conversion to char16_t: 0x%x", (int)ordinal); + return -1; + } + ordinal -= 0x10000; + *result++ = 0xD800 | (ordinal >> 10); + *result++ = 0xDC00 | (ordinal & 0x3FF); + continue; + } +#endif + *result++ = ordinal; + } + return 0; +} + +static int _my_PyUnicode_AsChar32(PyObject *unicode, + cffi_char32_t *result, + Py_ssize_t resultlen) +{ + Py_UNICODE *u = PyUnicode_AS_UNICODE(unicode); + Py_ssize_t i; + for (i=0; i= 3.3. + * + * CPython 3.3 added support for sys.maxunicode == 0x10FFFF on all + * platforms, even ones with wchar_t limited to 2 bytes. As such, + * this code here works from the outside like wchar_helper.h in the + * case Py_UNICODE_SIZE == 4, but the implementation is very different. + */ + +typedef uint16_t cffi_char16_t; +typedef uint32_t cffi_char32_t; + + +static PyObject * +_my_PyUnicode_FromChar32(const cffi_char32_t *w, Py_ssize_t size) +{ + return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, w, size); +} + +static PyObject * +_my_PyUnicode_FromChar16(const cffi_char16_t *w, Py_ssize_t size) +{ + /* are there any surrogate pairs, and if so, how many? */ + Py_ssize_t i, count_surrogates = 0; + for (i = 0; i < size - 1; i++) { + if (0xD800 <= w[i] && w[i] <= 0xDBFF && + 0xDC00 <= w[i+1] && w[i+1] <= 0xDFFF) + count_surrogates++; + } + if (count_surrogates == 0) { + /* no, fast path */ + return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, w, size); + } + else + { + PyObject *result = PyUnicode_New(size - count_surrogates, 0x10FFFF); + Py_UCS4 *data; + assert(PyUnicode_KIND(result) == PyUnicode_4BYTE_KIND); + data = PyUnicode_4BYTE_DATA(result); + + for (i = 0; i < size; i++) + { + cffi_char32_t ch = w[i]; + if (0xD800 <= ch && ch <= 0xDBFF && i < size - 1) { + cffi_char32_t ch2 = w[i + 1]; + if (0xDC00 <= ch2 && ch2 <= 0xDFFF) { + ch = (((ch & 0x3FF)<<10) | (ch2 & 0x3FF)) + 0x10000; + i++; + } + } + *data++ = ch; + } + return result; + } +} + +static int +_my_PyUnicode_AsSingleChar16(PyObject *unicode, cffi_char16_t *result, + char *err_got) +{ + cffi_char32_t ch; + if (PyUnicode_GET_LENGTH(unicode) != 1) { + sprintf(err_got, "unicode string of length %zd", + PyUnicode_GET_LENGTH(unicode)); + return -1; + } + ch = PyUnicode_READ_CHAR(unicode, 0); + + if (ch > 0xFFFF) + { + sprintf(err_got, "larger-than-0xFFFF character"); + return -1; + } + *result = (cffi_char16_t)ch; + return 0; +} + +static int +_my_PyUnicode_AsSingleChar32(PyObject *unicode, cffi_char32_t *result, + char *err_got) +{ + if (PyUnicode_GET_LENGTH(unicode) != 1) { + sprintf(err_got, "unicode string of length %zd", + PyUnicode_GET_LENGTH(unicode)); + return -1; + } + *result = PyUnicode_READ_CHAR(unicode, 0); + return 0; +} + +static Py_ssize_t _my_PyUnicode_SizeAsChar16(PyObject *unicode) +{ + Py_ssize_t length = PyUnicode_GET_LENGTH(unicode); + Py_ssize_t result = length; + unsigned int kind = PyUnicode_KIND(unicode); + + if (kind == PyUnicode_4BYTE_KIND) + { + Py_UCS4 *data = PyUnicode_4BYTE_DATA(unicode); + Py_ssize_t i; + for (i = 0; i < length; i++) { + if (data[i] > 0xFFFF) + result++; + } + } + return result; +} + +static Py_ssize_t _my_PyUnicode_SizeAsChar32(PyObject *unicode) +{ + return PyUnicode_GET_LENGTH(unicode); +} + +static int _my_PyUnicode_AsChar16(PyObject *unicode, + cffi_char16_t *result, + Py_ssize_t resultlen) +{ + Py_ssize_t len = PyUnicode_GET_LENGTH(unicode); + unsigned int kind = PyUnicode_KIND(unicode); + void *data = PyUnicode_DATA(unicode); + Py_ssize_t i; + + for (i = 0; i < len; i++) { + cffi_char32_t ordinal = PyUnicode_READ(kind, data, i); + if (ordinal > 0xFFFF) { + if (ordinal > 0x10FFFF) { + PyErr_Format(PyExc_ValueError, + "unicode character out of range for " + "conversion to char16_t: 0x%x", (int)ordinal); + return -1; + } + ordinal -= 0x10000; + *result++ = 0xD800 | (ordinal >> 10); + *result++ = 0xDC00 | (ordinal & 0x3FF); + } + else + *result++ = ordinal; + } + return 0; +} + +static int _my_PyUnicode_AsChar32(PyObject *unicode, + cffi_char32_t *result, + Py_ssize_t resultlen) +{ + if (PyUnicode_AsUCS4(unicode, (Py_UCS4 *)result, resultlen, 0) == NULL) + return -1; + return 0; +} diff --git a/cffi/Android.bp b/cffi/Android.bp new file mode 100644 index 0000000..e25787e --- /dev/null +++ b/cffi/Android.bp @@ -0,0 +1,44 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +python_library { + name: "py-cffi", + host_supported: true, + srcs: [ + "*.py", + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: true, + }, + }, + data: [ + ":py-cffi-headers", + ], + libs: [ + "py-cffi-backend", + "py-cffi-backend-libffi", + "py-pycparser", + ], + pkg_path: "cffi", +} + +filegroup { + name: "py-cffi-headers", + srcs: [ + "*.h", + ], +} diff --git a/cffi/__init__.py b/cffi/__init__.py new file mode 100644 index 0000000..5ebb64b --- /dev/null +++ b/cffi/__init__.py @@ -0,0 +1,14 @@ +__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError', + 'FFIError'] + +from .api import FFI +from .error import CDefError, FFIError, VerificationError, VerificationMissing +from .error import PkgConfigError + +__version__ = "1.12.2" +__version_info__ = (1, 12, 2) + +# The verifier module file names are based on the CRC32 of a string that +# contains the following version number. It may be older than __version__ +# if nothing is clearly incompatible. +__version_verifier_modules__ = "0.8.6" diff --git a/cffi/_cffi_errors.h b/cffi/_cffi_errors.h new file mode 100644 index 0000000..83cdad0 --- /dev/null +++ b/cffi/_cffi_errors.h @@ -0,0 +1,147 @@ +#ifndef CFFI_MESSAGEBOX +# ifdef _MSC_VER +# define CFFI_MESSAGEBOX 1 +# else +# define CFFI_MESSAGEBOX 0 +# endif +#endif + + +#if CFFI_MESSAGEBOX +/* Windows only: logic to take the Python-CFFI embedding logic + initialization errors and display them in a background thread + with MessageBox. The idea is that if the whole program closes + as a result of this problem, then likely it is already a console + program and you can read the stderr output in the console too. + If it is not a console program, then it will likely show its own + dialog to complain, or generally not abruptly close, and for this + case the background thread should stay alive. +*/ +static void *volatile _cffi_bootstrap_text; + +static PyObject *_cffi_start_error_capture(void) +{ + PyObject *result = NULL; + PyObject *x, *m, *bi; + + if (InterlockedCompareExchangePointer(&_cffi_bootstrap_text, + (void *)1, NULL) != NULL) + return (PyObject *)1; + + m = PyImport_AddModule("_cffi_error_capture"); + if (m == NULL) + goto error; + + result = PyModule_GetDict(m); + if (result == NULL) + goto error; + +#if PY_MAJOR_VERSION >= 3 + bi = PyImport_ImportModule("builtins"); +#else + bi = PyImport_ImportModule("__builtin__"); +#endif + if (bi == NULL) + goto error; + PyDict_SetItemString(result, "__builtins__", bi); + Py_DECREF(bi); + + x = PyRun_String( + "import sys\n" + "class FileLike:\n" + " def write(self, x):\n" + " try:\n" + " of.write(x)\n" + " except: pass\n" + " self.buf += x\n" + "fl = FileLike()\n" + "fl.buf = ''\n" + "of = sys.stderr\n" + "sys.stderr = fl\n" + "def done():\n" + " sys.stderr = of\n" + " return fl.buf\n", /* make sure the returned value stays alive */ + Py_file_input, + result, result); + Py_XDECREF(x); + + error: + if (PyErr_Occurred()) + { + PyErr_WriteUnraisable(Py_None); + PyErr_Clear(); + } + return result; +} + +#pragma comment(lib, "user32.lib") + +static DWORD WINAPI _cffi_bootstrap_dialog(LPVOID ignored) +{ + Sleep(666); /* may be interrupted if the whole process is closing */ +#if PY_MAJOR_VERSION >= 3 + MessageBoxW(NULL, (wchar_t *)_cffi_bootstrap_text, + L"Python-CFFI error", + MB_OK | MB_ICONERROR); +#else + MessageBoxA(NULL, (char *)_cffi_bootstrap_text, + "Python-CFFI error", + MB_OK | MB_ICONERROR); +#endif + _cffi_bootstrap_text = NULL; + return 0; +} + +static void _cffi_stop_error_capture(PyObject *ecap) +{ + PyObject *s; + void *text; + + if (ecap == (PyObject *)1) + return; + + if (ecap == NULL) + goto error; + + s = PyRun_String("done()", Py_eval_input, ecap, ecap); + if (s == NULL) + goto error; + + /* Show a dialog box, but in a background thread, and + never show multiple dialog boxes at once. */ +#if PY_MAJOR_VERSION >= 3 + text = PyUnicode_AsWideCharString(s, NULL); +#else + text = PyString_AsString(s); +#endif + + _cffi_bootstrap_text = text; + + if (text != NULL) + { + HANDLE h; + h = CreateThread(NULL, 0, _cffi_bootstrap_dialog, + NULL, 0, NULL); + if (h != NULL) + CloseHandle(h); + } + /* decref the string, but it should stay alive as 'fl.buf' + in the small module above. It will really be freed only if + we later get another similar error. So it's a leak of at + most one copy of the small module. That's fine for this + situation which is usually a "fatal error" anyway. */ + Py_DECREF(s); + PyErr_Clear(); + return; + + error: + _cffi_bootstrap_text = NULL; + PyErr_Clear(); +} + +#else + +static PyObject *_cffi_start_error_capture(void) { return NULL; } +static void _cffi_stop_error_capture(PyObject *ecap) { } + +#endif diff --git a/cffi/_cffi_include.h b/cffi/_cffi_include.h new file mode 100644 index 0000000..37ea74f --- /dev/null +++ b/cffi/_cffi_include.h @@ -0,0 +1,308 @@ +#define _CFFI_ + +/* We try to define Py_LIMITED_API before including Python.h. + + Mess: we can only define it if Py_DEBUG, Py_TRACE_REFS and + Py_REF_DEBUG are not defined. This is a best-effort approximation: + we can learn about Py_DEBUG from pyconfig.h, but it is unclear if + the same works for the other two macros. Py_DEBUG implies them, + but not the other way around. + + Issue #350 is still open: on Windows, the code here causes it to link + with PYTHON36.DLL (for example) instead of PYTHON3.DLL. A fix was + attempted in 164e526a5515 and 14ce6985e1c3, but reverted: virtualenv + does not make PYTHON3.DLL available, and so the "correctly" compiled + version would not run inside a virtualenv. We will re-apply the fix + after virtualenv has been fixed for some time. For explanation, see + issue #355. For a workaround if you want PYTHON3.DLL and don't worry + about virtualenv, see issue #350. See also 'py_limited_api' in + setuptools_ext.py. +*/ +#if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API) +# include +# if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) +# define Py_LIMITED_API +# endif +#endif + +#include +#ifdef __cplusplus +extern "C" { +#endif +#include +#include "parse_c_type.h" + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +#endif + +#ifdef __GNUC__ +# define _CFFI_UNUSED_FN __attribute__((unused)) +#else +# define _CFFI_UNUSED_FN /* nothing */ +#endif + +#ifdef __cplusplus +# ifndef _Bool + typedef bool _Bool; /* semi-hackish: C++ has no _Bool; bool is builtin */ +# endif +#endif + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, struct _cffi_ctypedescr *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + not used any more +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((_cffi_wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(_cffi_wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(struct _cffi_ctypedescr *, \ + PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[24]) +#define _CFFI_CPIDX 25 +#define _cffi_call_python \ + ((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[_CFFI_CPIDX]) +#define _cffi_to_c_wchar3216_t \ + ((int(*)(PyObject *))_cffi_exports[26]) +#define _cffi_from_c_wchar3216_t \ + ((PyObject *(*)(int))_cffi_exports[27]) +#define _CFFI_NUM_EXPORTS 28 + +struct _cffi_ctypedescr; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; + +#define _cffi_type(index) ( \ + assert((((uintptr_t)_cffi_types[index]) & 1) == 0), \ + (struct _cffi_ctypedescr *)_cffi_types[index]) + +static PyObject *_cffi_init(const char *module_name, Py_ssize_t version, + const struct _cffi_type_context_s *ctx) +{ + PyObject *module, *o_arg, *new_module; + void *raw[] = { + (void *)module_name, + (void *)version, + (void *)_cffi_exports, + (void *)ctx, + }; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + o_arg = PyLong_FromVoidPtr((void *)raw); + if (o_arg == NULL) + goto failure; + + new_module = PyObject_CallMethod( + module, (char *)"_init_cffi_1_0_external_module", (char *)"O", o_arg); + + Py_DECREF(o_arg); + Py_DECREF(module); + return new_module; + + failure: + Py_XDECREF(module); + return NULL; +} + + +#ifdef HAVE_WCHAR_H +typedef wchar_t _cffi_wchar_t; +#else +typedef uint16_t _cffi_wchar_t; /* same random pick as _cffi_backend.c */ +#endif + +_CFFI_UNUSED_FN static uint16_t _cffi_to_c_char16_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 2) + return (uint16_t)_cffi_to_c_wchar_t(o); + else + return (uint16_t)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char16_t(uint16_t x) +{ + if (sizeof(_cffi_wchar_t) == 2) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +_CFFI_UNUSED_FN static int _cffi_to_c_char32_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 4) + return (int)_cffi_to_c_wchar_t(o); + else + return (int)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char32_t(int x) +{ + if (sizeof(_cffi_wchar_t) == 4) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t(x); +} + + +/********** end CPython-specific section **********/ +#else +_CFFI_UNUSED_FN +static void (*_cffi_call_python_org)(struct _cffi_externpy_s *, char *); +# define _cffi_call_python _cffi_call_python_org +#endif + + +#define _cffi_array_len(array) (sizeof(array) / sizeof((array)[0])) + +#define _cffi_prim_int(size, sign) \ + ((size) == 1 ? ((sign) ? _CFFI_PRIM_INT8 : _CFFI_PRIM_UINT8) : \ + (size) == 2 ? ((sign) ? _CFFI_PRIM_INT16 : _CFFI_PRIM_UINT16) : \ + (size) == 4 ? ((sign) ? _CFFI_PRIM_INT32 : _CFFI_PRIM_UINT32) : \ + (size) == 8 ? ((sign) ? _CFFI_PRIM_INT64 : _CFFI_PRIM_UINT64) : \ + _CFFI__UNKNOWN_PRIM) + +#define _cffi_prim_float(size) \ + ((size) == sizeof(float) ? _CFFI_PRIM_FLOAT : \ + (size) == sizeof(double) ? _CFFI_PRIM_DOUBLE : \ + (size) == sizeof(long double) ? _CFFI__UNKNOWN_LONG_DOUBLE : \ + _CFFI__UNKNOWN_FLOAT_PRIM) + +#define _cffi_check_int(got, got_nonpos, expected) \ + ((got_nonpos) == (expected <= 0) && \ + (got) == (unsigned long long)expected) + +#ifdef MS_WIN32 +# define _cffi_stdcall __stdcall +#else +# define _cffi_stdcall /* nothing */ +#endif + +#ifdef __cplusplus +} +#endif diff --git a/cffi/_embedding.h b/cffi/_embedding.h new file mode 100644 index 0000000..3953cd7 --- /dev/null +++ b/cffi/_embedding.h @@ -0,0 +1,484 @@ + +/***** Support code for embedding *****/ + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(_WIN32) +# define CFFI_DLLEXPORT __declspec(dllexport) +#elif defined(__GNUC__) +# define CFFI_DLLEXPORT __attribute__((visibility("default"))) +#else +# define CFFI_DLLEXPORT /* nothing */ +#endif + + +/* There are two global variables of type _cffi_call_python_fnptr: + + * _cffi_call_python, which we declare just below, is the one called + by ``extern "Python"`` implementations. + + * _cffi_call_python_org, which on CPython is actually part of the + _cffi_exports[] array, is the function pointer copied from + _cffi_backend. + + After initialization is complete, both are equal. However, the + first one remains equal to &_cffi_start_and_call_python until the + very end of initialization, when we are (or should be) sure that + concurrent threads also see a completely initialized world, and + only then is it changed. +*/ +#undef _cffi_call_python +typedef void (*_cffi_call_python_fnptr)(struct _cffi_externpy_s *, char *); +static void _cffi_start_and_call_python(struct _cffi_externpy_s *, char *); +static _cffi_call_python_fnptr _cffi_call_python = &_cffi_start_and_call_python; + + +#ifndef _MSC_VER + /* --- Assuming a GCC not infinitely old --- */ +# define cffi_compare_and_swap(l,o,n) __sync_bool_compare_and_swap(l,o,n) +# define cffi_write_barrier() __sync_synchronize() +# if !defined(__amd64__) && !defined(__x86_64__) && \ + !defined(__i386__) && !defined(__i386) +# define cffi_read_barrier() __sync_synchronize() +# else +# define cffi_read_barrier() (void)0 +# endif +#else + /* --- Windows threads version --- */ +# include +# define cffi_compare_and_swap(l,o,n) \ + (InterlockedCompareExchangePointer(l,n,o) == (o)) +# define cffi_write_barrier() InterlockedCompareExchange(&_cffi_dummy,0,0) +# define cffi_read_barrier() (void)0 +static volatile LONG _cffi_dummy; +#endif + +#ifdef WITH_THREAD +# ifndef _MSC_VER +# include + static pthread_mutex_t _cffi_embed_startup_lock; +# else + static CRITICAL_SECTION _cffi_embed_startup_lock; +# endif + static char _cffi_embed_startup_lock_ready = 0; +#endif + +static void _cffi_acquire_reentrant_mutex(void) +{ + static void *volatile lock = NULL; + + while (!cffi_compare_and_swap(&lock, NULL, (void *)1)) { + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: pthread_mutex_init() should be very fast, and + this is only run at start-up anyway. */ + } + +#ifdef WITH_THREAD + if (!_cffi_embed_startup_lock_ready) { +# ifndef _MSC_VER + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&_cffi_embed_startup_lock, &attr); +# else + InitializeCriticalSection(&_cffi_embed_startup_lock); +# endif + _cffi_embed_startup_lock_ready = 1; + } +#endif + + while (!cffi_compare_and_swap(&lock, (void *)1, NULL)) + ; + +#ifndef _MSC_VER + pthread_mutex_lock(&_cffi_embed_startup_lock); +#else + EnterCriticalSection(&_cffi_embed_startup_lock); +#endif +} + +static void _cffi_release_reentrant_mutex(void) +{ +#ifndef _MSC_VER + pthread_mutex_unlock(&_cffi_embed_startup_lock); +#else + LeaveCriticalSection(&_cffi_embed_startup_lock); +#endif +} + + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + +#include "_cffi_errors.h" + + +#define _cffi_call_python_org _cffi_exports[_CFFI_CPIDX] + +PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(void); /* forward */ + +static void _cffi_py_initialize(void) +{ + /* XXX use initsigs=0, which "skips initialization registration of + signal handlers, which might be useful when Python is + embedded" according to the Python docs. But review and think + if it should be a user-controllable setting. + + XXX we should also give a way to write errors to a buffer + instead of to stderr. + + XXX if importing 'site' fails, CPython (any version) calls + exit(). Should we try to work around this behavior here? + */ + Py_InitializeEx(0); +} + +static int _cffi_initialize_python(void) +{ + /* This initializes Python, imports _cffi_backend, and then the + present .dll/.so is set up as a CPython C extension module. + */ + int result; + PyGILState_STATE state; + PyObject *pycode=NULL, *global_dict=NULL, *x; + + state = PyGILState_Ensure(); + + /* Call the initxxx() function from the present module. It will + create and initialize us as a CPython extension module, instead + of letting the startup Python code do it---it might reimport + the same .dll/.so and get maybe confused on some platforms. + It might also have troubles locating the .dll/.so again for all + I know. + */ + (void)_CFFI_PYTHON_STARTUP_FUNC(); + if (PyErr_Occurred()) + goto error; + + /* Now run the Python code provided to ffi.embedding_init_code(). + */ + pycode = Py_CompileString(_CFFI_PYTHON_STARTUP_CODE, + "", + Py_file_input); + if (pycode == NULL) + goto error; + global_dict = PyDict_New(); + if (global_dict == NULL) + goto error; + if (PyDict_SetItemString(global_dict, "__builtins__", + PyThreadState_GET()->interp->builtins) < 0) + goto error; + x = PyEval_EvalCode( +#if PY_MAJOR_VERSION < 3 + (PyCodeObject *) +#endif + pycode, global_dict, global_dict); + if (x == NULL) + goto error; + Py_DECREF(x); + + /* Done! Now if we've been called from + _cffi_start_and_call_python() in an ``extern "Python"``, we can + only hope that the Python code did correctly set up the + corresponding @ffi.def_extern() function. Otherwise, the + general logic of ``extern "Python"`` functions (inside the + _cffi_backend module) will find that the reference is still + missing and print an error. + */ + result = 0; + done: + Py_XDECREF(pycode); + Py_XDECREF(global_dict); + PyGILState_Release(state); + return result; + + error:; + { + /* Print as much information as potentially useful. + Debugging load-time failures with embedding is not fun + */ + PyObject *ecap; + PyObject *exception, *v, *tb, *f, *modules, *mod; + PyErr_Fetch(&exception, &v, &tb); + ecap = _cffi_start_error_capture(); + f = PySys_GetObject((char *)"stderr"); + if (f != NULL && f != Py_None) { + PyFile_WriteString( + "Failed to initialize the Python-CFFI embedding logic:\n\n", f); + } + + if (exception != NULL) { + PyErr_NormalizeException(&exception, &v, &tb); + PyErr_Display(exception, v, tb); + } + Py_XDECREF(exception); + Py_XDECREF(v); + Py_XDECREF(tb); + + if (f != NULL && f != Py_None) { + PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME + "\ncompiled with cffi version: 1.12.2" + "\n_cffi_backend module: ", f); + modules = PyImport_GetModuleDict(); + mod = PyDict_GetItemString(modules, "_cffi_backend"); + if (mod == NULL) { + PyFile_WriteString("not loaded", f); + } + else { + v = PyObject_GetAttrString(mod, "__file__"); + PyFile_WriteObject(v, f, 0); + Py_XDECREF(v); + } + PyFile_WriteString("\nsys.path: ", f); + PyFile_WriteObject(PySys_GetObject((char *)"path"), f, 0); + PyFile_WriteString("\n\n", f); + } + _cffi_stop_error_capture(ecap); + } + result = -1; + goto done; +} + +PyAPI_DATA(char *) _PyParser_TokenNames[]; /* from CPython */ + +static int _cffi_carefully_make_gil(void) +{ + /* This does the basic initialization of Python. It can be called + completely concurrently from unrelated threads. It assumes + that we don't hold the GIL before (if it exists), and we don't + hold it afterwards. + + (What it really does used to be completely different in Python 2 + and Python 3, with the Python 2 solution avoiding the spin-lock + around the Py_InitializeEx() call. However, after recent changes + to CPython 2.7 (issue #358) it no longer works. So we use the + Python 3 solution everywhere.) + + This initializes Python by calling Py_InitializeEx(). + Important: this must not be called concurrently at all. + So we use a global variable as a simple spin lock. This global + variable must be from 'libpythonX.Y.so', not from this + cffi-based extension module, because it must be shared from + different cffi-based extension modules. We choose + _PyParser_TokenNames[0] as a completely arbitrary pointer value + that is never written to. The default is to point to the + string "ENDMARKER". We change it temporarily to point to the + next character in that string. (Yes, I know it's REALLY + obscure.) + */ + +#ifdef WITH_THREAD + char *volatile *lock = (char *volatile *)_PyParser_TokenNames; + char *old_value; + + while (1) { /* spin loop */ + old_value = *lock; + if (old_value[0] == 'E') { + assert(old_value[1] == 'N'); + if (cffi_compare_and_swap(lock, old_value, old_value + 1)) + break; + } + else { + assert(old_value[0] == 'N'); + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: PyEval_InitThreads() should be very fast, and + this is only run at start-up anyway. */ + } + } +#endif + + /* call Py_InitializeEx() */ + { + PyGILState_STATE state = PyGILState_UNLOCKED; + if (!Py_IsInitialized()) + _cffi_py_initialize(); + else + state = PyGILState_Ensure(); + + PyEval_InitThreads(); + PyGILState_Release(state); + } + +#ifdef WITH_THREAD + /* release the lock */ + while (!cffi_compare_and_swap(lock, old_value + 1, old_value)) + ; +#endif + + return 0; +} + +/********** end CPython-specific section **********/ + + +#else + + +/********** PyPy-specific section **********/ + +PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(const void *[]); /* forward */ + +static struct _cffi_pypy_init_s { + const char *name; + void (*func)(const void *[]); + const char *code; +} _cffi_pypy_init = { + _CFFI_MODULE_NAME, + (void(*)(const void *[]))_CFFI_PYTHON_STARTUP_FUNC, + _CFFI_PYTHON_STARTUP_CODE, +}; + +extern int pypy_carefully_make_gil(const char *); +extern int pypy_init_embedded_cffi_module(int, struct _cffi_pypy_init_s *); + +static int _cffi_carefully_make_gil(void) +{ + return pypy_carefully_make_gil(_CFFI_MODULE_NAME); +} + +static int _cffi_initialize_python(void) +{ + return pypy_init_embedded_cffi_module(0xB011, &_cffi_pypy_init); +} + +/********** end PyPy-specific section **********/ + + +#endif + + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static _cffi_call_python_fnptr _cffi_start_python(void) +{ + /* Delicate logic to initialize Python. This function can be + called multiple times concurrently, e.g. when the process calls + its first ``extern "Python"`` functions in multiple threads at + once. It can also be called recursively, in which case we must + ignore it. We also have to consider what occurs if several + different cffi-based extensions reach this code in parallel + threads---it is a different copy of the code, then, and we + can't have any shared global variable unless it comes from + 'libpythonX.Y.so'. + + Idea: + + * _cffi_carefully_make_gil(): "carefully" call + PyEval_InitThreads() (possibly with Py_InitializeEx() first). + + * then we use a (local) custom lock to make sure that a call to this + cffi-based extension will wait if another call to the *same* + extension is running the initialization in another thread. + It is reentrant, so that a recursive call will not block, but + only one from a different thread. + + * then we grab the GIL and (Python 2) we call Py_InitializeEx(). + At this point, concurrent calls to Py_InitializeEx() are not + possible: we have the GIL. + + * do the rest of the specific initialization, which may + temporarily release the GIL but not the custom lock. + Only release the custom lock when we are done. + */ + static char called = 0; + + if (_cffi_carefully_make_gil() != 0) + return NULL; + + _cffi_acquire_reentrant_mutex(); + + /* Here the GIL exists, but we don't have it. We're only protected + from concurrency by the reentrant mutex. */ + + /* This file only initializes the embedded module once, the first + time this is called, even if there are subinterpreters. */ + if (!called) { + called = 1; /* invoke _cffi_initialize_python() only once, + but don't set '_cffi_call_python' right now, + otherwise concurrent threads won't call + this function at all (we need them to wait) */ + if (_cffi_initialize_python() == 0) { + /* now initialization is finished. Switch to the fast-path. */ + + /* We would like nobody to see the new value of + '_cffi_call_python' without also seeing the rest of the + data initialized. However, this is not possible. But + the new value of '_cffi_call_python' is the function + 'cffi_call_python()' from _cffi_backend. So: */ + cffi_write_barrier(); + /* ^^^ we put a write barrier here, and a corresponding + read barrier at the start of cffi_call_python(). This + ensures that after that read barrier, we see everything + done here before the write barrier. + */ + + assert(_cffi_call_python_org != NULL); + _cffi_call_python = (_cffi_call_python_fnptr)_cffi_call_python_org; + } + else { + /* initialization failed. Reset this to NULL, even if it was + already set to some other value. Future calls to + _cffi_start_python() are still forced to occur, and will + always return NULL from now on. */ + _cffi_call_python_org = NULL; + } + } + + _cffi_release_reentrant_mutex(); + + return (_cffi_call_python_fnptr)_cffi_call_python_org; +} + +static +void _cffi_start_and_call_python(struct _cffi_externpy_s *externpy, char *args) +{ + _cffi_call_python_fnptr fnptr; + int current_err = errno; +#ifdef _MSC_VER + int current_lasterr = GetLastError(); +#endif + fnptr = _cffi_start_python(); + if (fnptr == NULL) { + fprintf(stderr, "function %s() called, but initialization code " + "failed. Returning 0.\n", externpy->name); + memset(args, 0, externpy->size_of_result); + } +#ifdef _MSC_VER + SetLastError(current_lasterr); +#endif + errno = current_err; + + if (fnptr != NULL) + fnptr(externpy, args); +} + + +/* The cffi_start_python() function makes sure Python is initialized + and our cffi module is set up. It can be called manually from the + user C code. The same effect is obtained automatically from any + dll-exported ``extern "Python"`` function. This function returns + -1 if initialization failed, 0 if all is OK. */ +_CFFI_UNUSED_FN +static int cffi_start_python(void) +{ + if (_cffi_call_python == &_cffi_start_and_call_python) { + if (_cffi_start_python() == NULL) + return -1; + } + cffi_read_barrier(); + return 0; +} + +#undef cffi_compare_and_swap +#undef cffi_write_barrier +#undef cffi_read_barrier + +#ifdef __cplusplus +} +#endif diff --git a/cffi/api.py b/cffi/api.py new file mode 100644 index 0000000..32fe620 --- /dev/null +++ b/cffi/api.py @@ -0,0 +1,961 @@ +import sys, types +from .lock import allocate_lock +from .error import CDefError +from . import model + +try: + callable +except NameError: + # Python 3.1 + from collections import Callable + callable = lambda x: isinstance(x, Callable) + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +_unspecified = object() + + + +class FFI(object): + r''' + The main top-level class that you instantiate once, or once per module. + + Example usage: + + ffi = FFI() + ffi.cdef(""" + int printf(const char *, ...); + """) + + C = ffi.dlopen(None) # standard library + -or- + C = ffi.verify() # use a C compiler: verify the decl above is right + + C.printf("hello, %s!\n", ffi.new("char[]", "world")) + ''' + + def __init__(self, backend=None): + """Create an FFI instance. The 'backend' argument is used to + select a non-default backend, mostly for tests. + """ + if backend is None: + # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with + # _cffi_backend.so compiled. + import _cffi_backend as backend + from . import __version__ + if backend.__version__ != __version__: + # bad version! Try to be as explicit as possible. + if hasattr(backend, '__file__'): + # CPython + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. When we import the top-level '_cffi_backend' extension module, we get version %s, located in %r. The two versions should be equal; check your installation." % ( + __version__, __file__, + backend.__version__, backend.__file__)) + else: + # PyPy + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. This interpreter comes with a built-in '_cffi_backend' module, which is version %s. The two versions should be equal; check your installation." % ( + __version__, __file__, backend.__version__)) + # (If you insist you can also try to pass the option + # 'backend=backend_ctypes.CTypesBackend()', but don't + # rely on it! It's probably not going to work well.) + + from . import cparser + self._backend = backend + self._lock = allocate_lock() + self._parser = cparser.Parser() + self._cached_btypes = {} + self._parsed_types = types.ModuleType('parsed_types').__dict__ + self._new_types = types.ModuleType('new_types').__dict__ + self._function_caches = [] + self._libraries = [] + self._cdefsources = [] + self._included_ffis = [] + self._windows_unicode = None + self._init_once_cache = {} + self._cdef_version = None + self._embedding = None + self._typecache = model.get_typecache(backend) + if hasattr(backend, 'set_ffi'): + backend.set_ffi(self) + for name in list(backend.__dict__): + if name.startswith('RTLD_'): + setattr(self, name, getattr(backend, name)) + # + with self._lock: + self.BVoidP = self._get_cached_btype(model.voidp_type) + self.BCharA = self._get_cached_btype(model.char_array_type) + if isinstance(backend, types.ModuleType): + # _cffi_backend: attach these constants to the class + if not hasattr(FFI, 'NULL'): + FFI.NULL = self.cast(self.BVoidP, 0) + FFI.CData, FFI.CType = backend._get_types() + else: + # ctypes backend: attach these constants to the instance + self.NULL = self.cast(self.BVoidP, 0) + self.CData, self.CType = backend._get_types() + self.buffer = backend.buffer + + def cdef(self, csource, override=False, packed=False, pack=None): + """Parse the given C source. This registers all declared functions, + types, and global variables. The functions and global variables can + then be accessed via either 'ffi.dlopen()' or 'ffi.verify()'. + The types can be used in 'ffi.new()' and other functions. + If 'packed' is specified as True, all structs declared inside this + cdef are packed, i.e. laid out without any field alignment at all. + Alternatively, 'pack' can be a small integer, and requests for + alignment greater than that are ignored (pack=1 is equivalent to + packed=True). + """ + self._cdef(csource, override=override, packed=packed, pack=pack) + + def embedding_api(self, csource, packed=False, pack=None): + self._cdef(csource, packed=packed, pack=pack, dllexport=True) + if self._embedding is None: + self._embedding = '' + + def _cdef(self, csource, override=False, **options): + if not isinstance(csource, str): # unicode, on Python 2 + if not isinstance(csource, basestring): + raise TypeError("cdef() argument must be a string") + csource = csource.encode('ascii') + with self._lock: + self._cdef_version = object() + self._parser.parse(csource, override=override, **options) + self._cdefsources.append(csource) + if override: + for cache in self._function_caches: + cache.clear() + finishlist = self._parser._recomplete + if finishlist: + self._parser._recomplete = [] + for tp in finishlist: + tp.finish_backend_type(self, finishlist) + + def dlopen(self, name, flags=0): + """Load and return a dynamic library identified by 'name'. + The standard C library can be loaded by passing None. + Note that functions and types declared by 'ffi.cdef()' are not + linked to a particular library, just like C headers; in the + library we only look for the actual (untyped) symbols. + """ + assert isinstance(name, basestring) or name is None + with self._lock: + lib, function_cache = _make_ffi_library(self, name, flags) + self._function_caches.append(function_cache) + self._libraries.append(lib) + return lib + + def dlclose(self, lib): + """Close a library obtained with ffi.dlopen(). After this call, + access to functions or variables from the library will fail + (possibly with a segmentation fault). + """ + type(lib).__cffi_close__(lib) + + def _typeof_locked(self, cdecl): + # call me with the lock! + key = cdecl + if key in self._parsed_types: + return self._parsed_types[key] + # + if not isinstance(cdecl, str): # unicode, on Python 2 + cdecl = cdecl.encode('ascii') + # + type = self._parser.parse_type(cdecl) + really_a_function_type = type.is_raw_function + if really_a_function_type: + type = type.as_function_pointer() + btype = self._get_cached_btype(type) + result = btype, really_a_function_type + self._parsed_types[key] = result + return result + + def _typeof(self, cdecl, consider_function_as_funcptr=False): + # string -> ctype object + try: + result = self._parsed_types[cdecl] + except KeyError: + with self._lock: + result = self._typeof_locked(cdecl) + # + btype, really_a_function_type = result + if really_a_function_type and not consider_function_as_funcptr: + raise CDefError("the type %r is a function type, not a " + "pointer-to-function type" % (cdecl,)) + return btype + + def typeof(self, cdecl): + """Parse the C type given as a string and return the + corresponding object. + It can also be used on 'cdata' instance to get its C type. + """ + if isinstance(cdecl, basestring): + return self._typeof(cdecl) + if isinstance(cdecl, self.CData): + return self._backend.typeof(cdecl) + if isinstance(cdecl, types.BuiltinFunctionType): + res = _builtin_function_type(cdecl) + if res is not None: + return res + if (isinstance(cdecl, types.FunctionType) + and hasattr(cdecl, '_cffi_base_type')): + with self._lock: + return self._get_cached_btype(cdecl._cffi_base_type) + raise TypeError(type(cdecl)) + + def sizeof(self, cdecl): + """Return the size in bytes of the argument. It can be a + string naming a C type, or a 'cdata' instance. + """ + if isinstance(cdecl, basestring): + BType = self._typeof(cdecl) + return self._backend.sizeof(BType) + else: + return self._backend.sizeof(cdecl) + + def alignof(self, cdecl): + """Return the natural alignment size in bytes of the C type + given as a string. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.alignof(cdecl) + + def offsetof(self, cdecl, *fields_or_indexes): + """Return the offset of the named field inside the given + structure or array, which must be given as a C type name. + You can give several field names in case of nested structures. + You can also give numeric values which correspond to array + items, in case of an array type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._typeoffsetof(cdecl, *fields_or_indexes)[1] + + def new(self, cdecl, init=None): + """Allocate an instance according to the specified C type and + return a pointer to it. The specified C type must be either a + pointer or an array: ``new('X *')`` allocates an X and returns + a pointer to it, whereas ``new('X[n]')`` allocates an array of + n X'es and returns an array referencing it (which works + mostly like a pointer, like in C). You can also use + ``new('X[]', n)`` to allocate an array of a non-constant + length n. + + The memory is initialized following the rules of declaring a + global variable in C: by default it is zero-initialized, but + an explicit initializer can be given which can be used to + fill all or part of the memory. + + When the returned object goes out of scope, the memory + is freed. In other words the returned object has + ownership of the value of type 'cdecl' that it points to. This + means that the raw data can be used as long as this object is + kept alive, but must not be used for a longer time. Be careful + about that when copying the pointer to the memory somewhere + else, e.g. into another structure. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.newp(cdecl, init) + + def new_allocator(self, alloc=None, free=None, + should_clear_after_alloc=True): + """Return a new allocator, i.e. a function that behaves like ffi.new() + but uses the provided low-level 'alloc' and 'free' functions. + + 'alloc' is called with the size as argument. If it returns NULL, a + MemoryError is raised. 'free' is called with the result of 'alloc' + as argument. Both can be either Python function or directly C + functions. If 'free' is None, then no free function is called. + If both 'alloc' and 'free' are None, the default is used. + + If 'should_clear_after_alloc' is set to False, then the memory + returned by 'alloc' is assumed to be already cleared (or you are + fine with garbage); otherwise CFFI will clear it. + """ + compiled_ffi = self._backend.FFI() + allocator = compiled_ffi.new_allocator(alloc, free, + should_clear_after_alloc) + def allocate(cdecl, init=None): + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return allocator(cdecl, init) + return allocate + + def cast(self, cdecl, source): + """Similar to a C cast: returns an instance of the named C + type initialized with the given 'source'. The source is + casted between integers or pointers of any type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.cast(cdecl, source) + + def string(self, cdata, maxlen=-1): + """Return a Python string (or unicode string) from the 'cdata'. + If 'cdata' is a pointer or array of characters or bytes, returns + the null-terminated string. The returned string extends until + the first null character, or at most 'maxlen' characters. If + 'cdata' is an array then 'maxlen' defaults to its length. + + If 'cdata' is a pointer or array of wchar_t, returns a unicode + string following the same rules. + + If 'cdata' is a single character or byte or a wchar_t, returns + it as a string or unicode string. + + If 'cdata' is an enum, returns the value of the enumerator as a + string, or 'NUMBER' if the value is out of range. + """ + return self._backend.string(cdata, maxlen) + + def unpack(self, cdata, length): + """Unpack an array of C data of the given length, + returning a Python string/unicode/list. + + If 'cdata' is a pointer to 'char', returns a byte string. + It does not stop at the first null. This is equivalent to: + ffi.buffer(cdata, length)[:] + + If 'cdata' is a pointer to 'wchar_t', returns a unicode string. + 'length' is measured in wchar_t's; it is not the size in bytes. + + If 'cdata' is a pointer to anything else, returns a list of + 'length' items. This is a faster equivalent to: + [cdata[i] for i in range(length)] + """ + return self._backend.unpack(cdata, length) + + #def buffer(self, cdata, size=-1): + # """Return a read-write buffer object that references the raw C data + # pointed to by the given 'cdata'. The 'cdata' must be a pointer or + # an array. Can be passed to functions expecting a buffer, or directly + # manipulated with: + # + # buf[:] get a copy of it in a regular string, or + # buf[idx] as a single character + # buf[:] = ... + # buf[idx] = ... change the content + # """ + # note that 'buffer' is a type, set on this instance by __init__ + + def from_buffer(self, cdecl, python_buffer=_unspecified, + require_writable=False): + """Return a cdata of the given type pointing to the data of the + given Python object, which must support the buffer interface. + Note that this is not meant to be used on the built-in types + str or unicode (you can build 'char[]' arrays explicitly) + but only on objects containing large quantities of raw data + in some other format, like 'array.array' or numpy arrays. + + The first argument is optional and default to 'char[]'. + """ + if python_buffer is _unspecified: + cdecl, python_buffer = self.BCharA, cdecl + elif isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.from_buffer(cdecl, python_buffer, + require_writable) + + def memmove(self, dest, src, n): + """ffi.memmove(dest, src, n) copies n bytes of memory from src to dest. + + Like the C function memmove(), the memory areas may overlap; + apart from that it behaves like the C function memcpy(). + + 'src' can be any cdata ptr or array, or any Python buffer object. + 'dest' can be any cdata ptr or array, or a writable Python buffer + object. The size to copy, 'n', is always measured in bytes. + + Unlike other methods, this one supports all Python buffer including + byte strings and bytearrays---but it still does not support + non-contiguous buffers. + """ + return self._backend.memmove(dest, src, n) + + def callback(self, cdecl, python_callable=None, error=None, onerror=None): + """Return a callback object or a decorator making such a + callback object. 'cdecl' must name a C function pointer type. + The callback invokes the specified 'python_callable' (which may + be provided either directly or via a decorator). Important: the + callback object must be manually kept alive for as long as the + callback may be invoked from the C level. + """ + def callback_decorator_wrap(python_callable): + if not callable(python_callable): + raise TypeError("the 'python_callable' argument " + "is not callable") + return self._backend.callback(cdecl, python_callable, + error, onerror) + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl, consider_function_as_funcptr=True) + if python_callable is None: + return callback_decorator_wrap # decorator mode + else: + return callback_decorator_wrap(python_callable) # direct mode + + def getctype(self, cdecl, replace_with=''): + """Return a string giving the C type 'cdecl', which may be itself + a string or a object. If 'replace_with' is given, it gives + extra text to append (or insert for more complicated C types), like + a variable name, or '*' to get actually the C type 'pointer-to-cdecl'. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + replace_with = replace_with.strip() + if (replace_with.startswith('*') + and '&[' in self._backend.getcname(cdecl, '&')): + replace_with = '(%s)' % replace_with + elif replace_with and not replace_with[0] in '[(': + replace_with = ' ' + replace_with + return self._backend.getcname(cdecl, replace_with) + + def gc(self, cdata, destructor, size=0): + """Return a new cdata object that points to the same + data. Later, when this new cdata object is garbage-collected, + 'destructor(old_cdata_object)' will be called. + + The optional 'size' gives an estimate of the size, used to + trigger the garbage collection more eagerly. So far only used + on PyPy. It tells the GC that the returned object keeps alive + roughly 'size' bytes of external memory. + """ + return self._backend.gcp(cdata, destructor, size) + + def _get_cached_btype(self, type): + assert self._lock.acquire(False) is False + # call me with the lock! + try: + BType = self._cached_btypes[type] + except KeyError: + finishlist = [] + BType = type.get_cached_btype(self, finishlist) + for type in finishlist: + type.finish_backend_type(self, finishlist) + return BType + + def verify(self, source='', tmpdir=None, **kwargs): + """Verify that the current ffi signatures compile on this + machine, and return a dynamic library object. The dynamic + library can be used to call functions and access global + variables declared in this 'ffi'. The library is compiled + by the C compiler: it gives you C-level API compatibility + (including calling macros). This is unlike 'ffi.dlopen()', + which requires binary compatibility in the signatures. + """ + from .verifier import Verifier, _caller_dir_pycache + # + # If set_unicode(True) was called, insert the UNICODE and + # _UNICODE macro declarations + if self._windows_unicode: + self._apply_windows_unicode(kwargs) + # + # Set the tmpdir here, and not in Verifier.__init__: it picks + # up the caller's directory, which we want to be the caller of + # ffi.verify(), as opposed to the caller of Veritier(). + tmpdir = tmpdir or _caller_dir_pycache() + # + # Make a Verifier() and use it to load the library. + self.verifier = Verifier(self, source, tmpdir, **kwargs) + lib = self.verifier.load_library() + # + # Save the loaded library for keep-alive purposes, even + # if the caller doesn't keep it alive itself (it should). + self._libraries.append(lib) + return lib + + def _get_errno(self): + return self._backend.get_errno() + def _set_errno(self, errno): + self._backend.set_errno(errno) + errno = property(_get_errno, _set_errno, None, + "the value of 'errno' from/to the C calls") + + def getwinerror(self, code=-1): + return self._backend.getwinerror(code) + + def _pointer_to(self, ctype): + with self._lock: + return model.pointer_cache(self, ctype) + + def addressof(self, cdata, *fields_or_indexes): + """Return the address of a . + If 'fields_or_indexes' are given, returns the address of that + field or array item in the structure or array, recursively in + case of nested structures. + """ + try: + ctype = self._backend.typeof(cdata) + except TypeError: + if '__addressof__' in type(cdata).__dict__: + return type(cdata).__addressof__(cdata, *fields_or_indexes) + raise + if fields_or_indexes: + ctype, offset = self._typeoffsetof(ctype, *fields_or_indexes) + else: + if ctype.kind == "pointer": + raise TypeError("addressof(pointer)") + offset = 0 + ctypeptr = self._pointer_to(ctype) + return self._backend.rawaddressof(ctypeptr, cdata, offset) + + def _typeoffsetof(self, ctype, field_or_index, *fields_or_indexes): + ctype, offset = self._backend.typeoffsetof(ctype, field_or_index) + for field1 in fields_or_indexes: + ctype, offset1 = self._backend.typeoffsetof(ctype, field1, 1) + offset += offset1 + return ctype, offset + + def include(self, ffi_to_include): + """Includes the typedefs, structs, unions and enums defined + in another FFI instance. Usage is similar to a #include in C, + where a part of the program might include types defined in + another part for its own usage. Note that the include() + method has no effect on functions, constants and global + variables, which must anyway be accessed directly from the + lib object returned by the original FFI instance. + """ + if not isinstance(ffi_to_include, FFI): + raise TypeError("ffi.include() expects an argument that is also of" + " type cffi.FFI, not %r" % ( + type(ffi_to_include).__name__,)) + if ffi_to_include is self: + raise ValueError("self.include(self)") + with ffi_to_include._lock: + with self._lock: + self._parser.include(ffi_to_include._parser) + self._cdefsources.append('[') + self._cdefsources.extend(ffi_to_include._cdefsources) + self._cdefsources.append(']') + self._included_ffis.append(ffi_to_include) + + def new_handle(self, x): + return self._backend.newp_handle(self.BVoidP, x) + + def from_handle(self, x): + return self._backend.from_handle(x) + + def release(self, x): + self._backend.release(x) + + def set_unicode(self, enabled_flag): + """Windows: if 'enabled_flag' is True, enable the UNICODE and + _UNICODE defines in C, and declare the types like TCHAR and LPTCSTR + to be (pointers to) wchar_t. If 'enabled_flag' is False, + declare these types to be (pointers to) plain 8-bit characters. + This is mostly for backward compatibility; you usually want True. + """ + if self._windows_unicode is not None: + raise ValueError("set_unicode() can only be called once") + enabled_flag = bool(enabled_flag) + if enabled_flag: + self.cdef("typedef wchar_t TBYTE;" + "typedef wchar_t TCHAR;" + "typedef const wchar_t *LPCTSTR;" + "typedef const wchar_t *PCTSTR;" + "typedef wchar_t *LPTSTR;" + "typedef wchar_t *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + else: + self.cdef("typedef char TBYTE;" + "typedef char TCHAR;" + "typedef const char *LPCTSTR;" + "typedef const char *PCTSTR;" + "typedef char *LPTSTR;" + "typedef char *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + self._windows_unicode = enabled_flag + + def _apply_windows_unicode(self, kwds): + defmacros = kwds.get('define_macros', ()) + if not isinstance(defmacros, (list, tuple)): + raise TypeError("'define_macros' must be a list or tuple") + defmacros = list(defmacros) + [('UNICODE', '1'), + ('_UNICODE', '1')] + kwds['define_macros'] = defmacros + + def _apply_embedding_fix(self, kwds): + # must include an argument like "-lpython2.7" for the compiler + def ensure(key, value): + lst = kwds.setdefault(key, []) + if value not in lst: + lst.append(value) + # + if '__pypy__' in sys.builtin_module_names: + import os + if sys.platform == "win32": + # we need 'libpypy-c.lib'. Current distributions of + # pypy (>= 4.1) contain it as 'libs/python27.lib'. + pythonlib = "python{0[0]}{0[1]}".format(sys.version_info) + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'libs')) + else: + # we need 'libpypy-c.{so,dylib}', which should be by + # default located in 'sys.prefix/bin' for installed + # systems. + if sys.version_info < (3,): + pythonlib = "pypy-c" + else: + pythonlib = "pypy3-c" + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'bin')) + # On uninstalled pypy's, the libpypy-c is typically found in + # .../pypy/goal/. + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'pypy', 'goal')) + else: + if sys.platform == "win32": + template = "python%d%d" + if hasattr(sys, 'gettotalrefcount'): + template += '_d' + else: + try: + import sysconfig + except ImportError: # 2.6 + from distutils import sysconfig + template = "python%d.%d" + if sysconfig.get_config_var('DEBUG_EXT'): + template += sysconfig.get_config_var('DEBUG_EXT') + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + if hasattr(sys, 'abiflags'): + pythonlib += sys.abiflags + ensure('libraries', pythonlib) + if sys.platform == "win32": + ensure('extra_link_args', '/MANIFEST') + + def set_source(self, module_name, source, source_extension='.c', **kwds): + import os + if hasattr(self, '_assigned_source'): + raise ValueError("set_source() cannot be called several times " + "per ffi object") + if not isinstance(module_name, basestring): + raise TypeError("'module_name' must be a string") + if os.sep in module_name or (os.altsep and os.altsep in module_name): + raise ValueError("'module_name' must not contain '/': use a dotted " + "name to make a 'package.module' location") + self._assigned_source = (str(module_name), source, + source_extension, kwds) + + def set_source_pkgconfig(self, module_name, pkgconfig_libs, source, + source_extension='.c', **kwds): + from . import pkgconfig + if not isinstance(pkgconfig_libs, list): + raise TypeError("the pkgconfig_libs argument must be a list " + "of package names") + kwds2 = pkgconfig.flags_from_pkgconfig(pkgconfig_libs) + pkgconfig.merge_flags(kwds, kwds2) + self.set_source(module_name, source, source_extension, **kwds) + + def distutils_extension(self, tmpdir='build', verbose=True): + from distutils.dir_util import mkpath + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + if hasattr(self, 'verifier'): # fallback, 'tmpdir' ignored + return self.verifier.get_extension() + raise ValueError("set_source() must be called before" + " distutils_extension()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("distutils_extension() is only for C extension " + "modules, not for dlopen()-style pure Python " + "modules") + mkpath(tmpdir) + ext, updated = recompile(self, module_name, + source, tmpdir=tmpdir, extradir=tmpdir, + source_extension=source_extension, + call_c_compiler=False, **kwds) + if verbose: + if updated: + sys.stderr.write("regenerated: %r\n" % (ext.sources[0],)) + else: + sys.stderr.write("not modified: %r\n" % (ext.sources[0],)) + return ext + + def emit_c_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("emit_c_code() is only for C extension modules, " + "not for dlopen()-style pure Python modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, **kwds) + + def emit_python_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is not None: + raise TypeError("emit_python_code() is only for dlopen()-style " + "pure Python modules, not for C extension modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, **kwds) + + def compile(self, tmpdir='.', verbose=0, target=None, debug=None): + """The 'target' argument gives the final file name of the + compiled DLL. Use '*' to force distutils' choice, suitable for + regular CPython C API modules. Use a file name ending in '.*' + to ask for the system's default extension for dynamic libraries + (.so/.dll/.dylib). + + The default is '*' when building a non-embedded C API extension, + and (module_name + '.*') when building an embedded library. + """ + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before compile()") + module_name, source, source_extension, kwds = self._assigned_source + return recompile(self, module_name, source, tmpdir=tmpdir, + target=target, source_extension=source_extension, + compiler_verbose=verbose, debug=debug, **kwds) + + def init_once(self, func, tag): + # Read _init_once_cache[tag], which is either (False, lock) if + # we're calling the function now in some thread, or (True, result). + # Don't call setdefault() in most cases, to avoid allocating and + # immediately freeing a lock; but still use setdefaut() to avoid + # races. + try: + x = self._init_once_cache[tag] + except KeyError: + x = self._init_once_cache.setdefault(tag, (False, allocate_lock())) + # Common case: we got (True, result), so we return the result. + if x[0]: + return x[1] + # Else, it's a lock. Acquire it to serialize the following tests. + with x[1]: + # Read again from _init_once_cache the current status. + x = self._init_once_cache[tag] + if x[0]: + return x[1] + # Call the function and store the result back. + result = func() + self._init_once_cache[tag] = (True, result) + return result + + def embedding_init_code(self, pysource): + if self._embedding: + raise ValueError("embedding_init_code() can only be called once") + # fix 'pysource' before it gets dumped into the C file: + # - remove empty lines at the beginning, so it starts at "line 1" + # - dedent, if all non-empty lines are indented + # - check for SyntaxErrors + import re + match = re.match(r'\s*\n', pysource) + if match: + pysource = pysource[match.end():] + lines = pysource.splitlines() or [''] + prefix = re.match(r'\s*', lines[0]).group() + for i in range(1, len(lines)): + line = lines[i] + if line.rstrip(): + while not line.startswith(prefix): + prefix = prefix[:-1] + i = len(prefix) + lines = [line[i:]+'\n' for line in lines] + pysource = ''.join(lines) + # + compile(pysource, "cffi_init", "exec") + # + self._embedding = pysource + + def def_extern(self, *args, **kwds): + raise ValueError("ffi.def_extern() is only available on API-mode FFI " + "objects") + + def list_types(self): + """Returns the user type names known to this FFI instance. + This returns a tuple containing three lists of names: + (typedef_names, names_of_structs, names_of_unions) + """ + typedefs = [] + structs = [] + unions = [] + for key in self._parser._declarations: + if key.startswith('typedef '): + typedefs.append(key[8:]) + elif key.startswith('struct '): + structs.append(key[7:]) + elif key.startswith('union '): + unions.append(key[6:]) + typedefs.sort() + structs.sort() + unions.sort() + return (typedefs, structs, unions) + + +def _load_backend_lib(backend, name, flags): + import os + if name is None: + if sys.platform != "win32": + return backend.load_library(None, flags) + name = "c" # Windows: load_library(None) fails, but this works + # on Python 2 (backward compatibility hack only) + first_error = None + if '.' in name or '/' in name or os.sep in name: + try: + return backend.load_library(name, flags) + except OSError as e: + first_error = e + import ctypes.util + path = ctypes.util.find_library(name) + if path is None: + if name == "c" and sys.platform == "win32" and sys.version_info >= (3,): + raise OSError("dlopen(None) cannot work on Windows for Python 3 " + "(see http://bugs.python.org/issue23606)") + msg = ("ctypes.util.find_library() did not manage " + "to locate a library called %r" % (name,)) + if first_error is not None: + msg = "%s. Additionally, %s" % (first_error, msg) + raise OSError(msg) + return backend.load_library(path, flags) + +def _make_ffi_library(ffi, libname, flags): + backend = ffi._backend + backendlib = _load_backend_lib(backend, libname, flags) + # + def accessor_function(name): + key = 'function ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + value = backendlib.load_function(BType, name) + library.__dict__[name] = value + # + def accessor_variable(name): + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + read_variable = backendlib.read_variable + write_variable = backendlib.write_variable + setattr(FFILibrary, name, property( + lambda self: read_variable(BType, name), + lambda self, value: write_variable(BType, name, value))) + # + def addressof_var(name): + try: + return addr_variables[name] + except KeyError: + with ffi._lock: + if name not in addr_variables: + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + if BType.kind != 'array': + BType = model.pointer_cache(ffi, BType) + p = backendlib.load_function(BType, name) + addr_variables[name] = p + return addr_variables[name] + # + def accessor_constant(name): + raise NotImplementedError("non-integer constant '%s' cannot be " + "accessed from a dlopen() library" % (name,)) + # + def accessor_int_constant(name): + library.__dict__[name] = ffi._parser._int_constants[name] + # + accessors = {} + accessors_version = [False] + addr_variables = {} + # + def update_accessors(): + if accessors_version[0] is ffi._cdef_version: + return + # + for key, (tp, _) in ffi._parser._declarations.items(): + if not isinstance(tp, model.EnumType): + tag, name = key.split(' ', 1) + if tag == 'function': + accessors[name] = accessor_function + elif tag == 'variable': + accessors[name] = accessor_variable + elif tag == 'constant': + accessors[name] = accessor_constant + else: + for i, enumname in enumerate(tp.enumerators): + def accessor_enum(name, tp=tp, i=i): + tp.check_not_partial() + library.__dict__[name] = tp.enumvalues[i] + accessors[enumname] = accessor_enum + for name in ffi._parser._int_constants: + accessors.setdefault(name, accessor_int_constant) + accessors_version[0] = ffi._cdef_version + # + def make_accessor(name): + with ffi._lock: + if name in library.__dict__ or name in FFILibrary.__dict__: + return # added by another thread while waiting for the lock + if name not in accessors: + update_accessors() + if name not in accessors: + raise AttributeError(name) + accessors[name](name) + # + class FFILibrary(object): + def __getattr__(self, name): + make_accessor(name) + return getattr(self, name) + def __setattr__(self, name, value): + try: + property = getattr(self.__class__, name) + except AttributeError: + make_accessor(name) + setattr(self, name, value) + else: + property.__set__(self, value) + def __dir__(self): + with ffi._lock: + update_accessors() + return accessors.keys() + def __addressof__(self, name): + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + make_accessor(name) + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + raise AttributeError("cffi library has no function or " + "global variable named '%s'" % (name,)) + def __cffi_close__(self): + backendlib.close_lib() + self.__dict__.clear() + # + if libname is not None: + try: + if not isinstance(libname, str): # unicode, on Python 2 + libname = libname.encode('utf-8') + FFILibrary.__name__ = 'FFILibrary_%s' % libname + except UnicodeError: + pass + library = FFILibrary() + return library, library.__dict__ + +def _builtin_function_type(func): + # a hack to make at least ffi.typeof(builtin_function) work, + # if the builtin function was obtained by 'vengine_cpy'. + import sys + try: + module = sys.modules[func.__module__] + ffi = module._cffi_original_ffi + types_of_builtin_funcs = module._cffi_types_of_builtin_funcs + tp = types_of_builtin_funcs[func] + except (KeyError, AttributeError, TypeError): + return None + else: + with ffi._lock: + return ffi._get_cached_btype(tp) diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py new file mode 100644 index 0000000..679ae05 --- /dev/null +++ b/cffi/backend_ctypes.py @@ -0,0 +1,1121 @@ +import ctypes, ctypes.util, operator, sys +from . import model + +if sys.version_info < (3,): + bytechr = chr +else: + unicode = str + long = int + xrange = range + bytechr = lambda num: bytes([num]) + +class CTypesType(type): + pass + +class CTypesData(object): + __metaclass__ = CTypesType + __slots__ = ['__weakref__'] + __name__ = '' + + def __init__(self, *args): + raise TypeError("cannot instantiate %r" % (self.__class__,)) + + @classmethod + def _newp(cls, init): + raise TypeError("expected a pointer or array ctype, got '%s'" + % (cls._get_c_name(),)) + + @staticmethod + def _to_ctypes(value): + raise TypeError + + @classmethod + def _arg_to_ctypes(cls, *value): + try: + ctype = cls._ctype + except AttributeError: + raise TypeError("cannot create an instance of %r" % (cls,)) + if value: + res = cls._to_ctypes(*value) + if not isinstance(res, ctype): + res = cls._ctype(res) + else: + res = cls._ctype() + return res + + @classmethod + def _create_ctype_obj(cls, init): + if init is None: + return cls._arg_to_ctypes() + else: + return cls._arg_to_ctypes(init) + + @staticmethod + def _from_ctypes(ctypes_value): + raise TypeError + + @classmethod + def _get_c_name(cls, replace_with=''): + return cls._reftypename.replace(' &', replace_with) + + @classmethod + def _fix_class(cls): + cls.__name__ = 'CData<%s>' % (cls._get_c_name(),) + cls.__qualname__ = 'CData<%s>' % (cls._get_c_name(),) + cls.__module__ = 'ffi' + + def _get_own_repr(self): + raise NotImplementedError + + def _addr_repr(self, address): + if address == 0: + return 'NULL' + else: + if address < 0: + address += 1 << (8*ctypes.sizeof(ctypes.c_void_p)) + return '0x%x' % address + + def __repr__(self, c_name=None): + own = self._get_own_repr() + return '' % (c_name or self._get_c_name(), own) + + def _convert_to_address(self, BClass): + if BClass is None: + raise TypeError("cannot convert %r to an address" % ( + self._get_c_name(),)) + else: + raise TypeError("cannot convert %r to %r" % ( + self._get_c_name(), BClass._get_c_name())) + + @classmethod + def _get_size(cls): + return ctypes.sizeof(cls._ctype) + + def _get_size_of_instance(self): + return ctypes.sizeof(self._ctype) + + @classmethod + def _cast_from(cls, source): + raise TypeError("cannot cast to %r" % (cls._get_c_name(),)) + + def _cast_to_integer(self): + return self._convert_to_address(None) + + @classmethod + def _alignment(cls): + return ctypes.alignment(cls._ctype) + + def __iter__(self): + raise TypeError("cdata %r does not support iteration" % ( + self._get_c_name()),) + + def _make_cmp(name): + cmpfunc = getattr(operator, name) + def cmp(self, other): + v_is_ptr = not isinstance(self, CTypesGenericPrimitive) + w_is_ptr = (isinstance(other, CTypesData) and + not isinstance(other, CTypesGenericPrimitive)) + if v_is_ptr and w_is_ptr: + return cmpfunc(self._convert_to_address(None), + other._convert_to_address(None)) + elif v_is_ptr or w_is_ptr: + return NotImplemented + else: + if isinstance(self, CTypesGenericPrimitive): + self = self._value + if isinstance(other, CTypesGenericPrimitive): + other = other._value + return cmpfunc(self, other) + cmp.func_name = name + return cmp + + __eq__ = _make_cmp('__eq__') + __ne__ = _make_cmp('__ne__') + __lt__ = _make_cmp('__lt__') + __le__ = _make_cmp('__le__') + __gt__ = _make_cmp('__gt__') + __ge__ = _make_cmp('__ge__') + + def __hash__(self): + return hash(self._convert_to_address(None)) + + def _to_string(self, maxlen): + raise TypeError("string(): %r" % (self,)) + + +class CTypesGenericPrimitive(CTypesData): + __slots__ = [] + + def __hash__(self): + return hash(self._value) + + def _get_own_repr(self): + return repr(self._from_ctypes(self._value)) + + +class CTypesGenericArray(CTypesData): + __slots__ = [] + + @classmethod + def _newp(cls, init): + return cls(init) + + def __iter__(self): + for i in xrange(len(self)): + yield self[i] + + def _get_own_repr(self): + return self._addr_repr(ctypes.addressof(self._blob)) + + +class CTypesGenericPtr(CTypesData): + __slots__ = ['_address', '_as_ctype_ptr'] + _automatic_casts = False + kind = "pointer" + + @classmethod + def _newp(cls, init): + return cls(init) + + @classmethod + def _cast_from(cls, source): + if source is None: + address = 0 + elif isinstance(source, CTypesData): + address = source._cast_to_integer() + elif isinstance(source, (int, long)): + address = source + else: + raise TypeError("bad type for cast to %r: %r" % + (cls, type(source).__name__)) + return cls._new_pointer_at(address) + + @classmethod + def _new_pointer_at(cls, address): + self = cls.__new__(cls) + self._address = address + self._as_ctype_ptr = ctypes.cast(address, cls._ctype) + return self + + def _get_own_repr(self): + try: + return self._addr_repr(self._address) + except AttributeError: + return '???' + + def _cast_to_integer(self): + return self._address + + def __nonzero__(self): + return bool(self._address) + __bool__ = __nonzero__ + + @classmethod + def _to_ctypes(cls, value): + if not isinstance(value, CTypesData): + raise TypeError("unexpected %s object" % type(value).__name__) + address = value._convert_to_address(cls) + return ctypes.cast(address, cls._ctype) + + @classmethod + def _from_ctypes(cls, ctypes_ptr): + address = ctypes.cast(ctypes_ptr, ctypes.c_void_p).value or 0 + return cls._new_pointer_at(address) + + @classmethod + def _initialize(cls, ctypes_ptr, value): + if value: + ctypes_ptr.contents = cls._to_ctypes(value).contents + + def _convert_to_address(self, BClass): + if (BClass in (self.__class__, None) or BClass._automatic_casts + or self._automatic_casts): + return self._address + else: + return CTypesData._convert_to_address(self, BClass) + + +class CTypesBaseStructOrUnion(CTypesData): + __slots__ = ['_blob'] + + @classmethod + def _create_ctype_obj(cls, init): + # may be overridden + raise TypeError("cannot instantiate opaque type %s" % (cls,)) + + def _get_own_repr(self): + return self._addr_repr(ctypes.addressof(self._blob)) + + @classmethod + def _offsetof(cls, fieldname): + return getattr(cls._ctype, fieldname).offset + + def _convert_to_address(self, BClass): + if getattr(BClass, '_BItem', None) is self.__class__: + return ctypes.addressof(self._blob) + else: + return CTypesData._convert_to_address(self, BClass) + + @classmethod + def _from_ctypes(cls, ctypes_struct_or_union): + self = cls.__new__(cls) + self._blob = ctypes_struct_or_union + return self + + @classmethod + def _to_ctypes(cls, value): + return value._blob + + def __repr__(self, c_name=None): + return CTypesData.__repr__(self, c_name or self._get_c_name(' &')) + + +class CTypesBackend(object): + + PRIMITIVE_TYPES = { + 'char': ctypes.c_char, + 'short': ctypes.c_short, + 'int': ctypes.c_int, + 'long': ctypes.c_long, + 'long long': ctypes.c_longlong, + 'signed char': ctypes.c_byte, + 'unsigned char': ctypes.c_ubyte, + 'unsigned short': ctypes.c_ushort, + 'unsigned int': ctypes.c_uint, + 'unsigned long': ctypes.c_ulong, + 'unsigned long long': ctypes.c_ulonglong, + 'float': ctypes.c_float, + 'double': ctypes.c_double, + '_Bool': ctypes.c_bool, + } + + for _name in ['unsigned long long', 'unsigned long', + 'unsigned int', 'unsigned short', 'unsigned char']: + _size = ctypes.sizeof(PRIMITIVE_TYPES[_name]) + PRIMITIVE_TYPES['uint%d_t' % (8*_size)] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_void_p): + PRIMITIVE_TYPES['uintptr_t'] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_size_t): + PRIMITIVE_TYPES['size_t'] = PRIMITIVE_TYPES[_name] + + for _name in ['long long', 'long', 'int', 'short', 'signed char']: + _size = ctypes.sizeof(PRIMITIVE_TYPES[_name]) + PRIMITIVE_TYPES['int%d_t' % (8*_size)] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_void_p): + PRIMITIVE_TYPES['intptr_t'] = PRIMITIVE_TYPES[_name] + PRIMITIVE_TYPES['ptrdiff_t'] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_size_t): + PRIMITIVE_TYPES['ssize_t'] = PRIMITIVE_TYPES[_name] + + + def __init__(self): + self.RTLD_LAZY = 0 # not supported anyway by ctypes + self.RTLD_NOW = 0 + self.RTLD_GLOBAL = ctypes.RTLD_GLOBAL + self.RTLD_LOCAL = ctypes.RTLD_LOCAL + + def set_ffi(self, ffi): + self.ffi = ffi + + def _get_types(self): + return CTypesData, CTypesType + + def load_library(self, path, flags=0): + cdll = ctypes.CDLL(path, flags) + return CTypesLibrary(self, cdll) + + def new_void_type(self): + class CTypesVoid(CTypesData): + __slots__ = [] + _reftypename = 'void &' + @staticmethod + def _from_ctypes(novalue): + return None + @staticmethod + def _to_ctypes(novalue): + if novalue is not None: + raise TypeError("None expected, got %s object" % + (type(novalue).__name__,)) + return None + CTypesVoid._fix_class() + return CTypesVoid + + def new_primitive_type(self, name): + if name == 'wchar_t': + raise NotImplementedError(name) + ctype = self.PRIMITIVE_TYPES[name] + if name == 'char': + kind = 'char' + elif name in ('float', 'double'): + kind = 'float' + else: + if name in ('signed char', 'unsigned char'): + kind = 'byte' + elif name == '_Bool': + kind = 'bool' + else: + kind = 'int' + is_signed = (ctype(-1).value == -1) + # + def _cast_source_to_int(source): + if isinstance(source, (int, long, float)): + source = int(source) + elif isinstance(source, CTypesData): + source = source._cast_to_integer() + elif isinstance(source, bytes): + source = ord(source) + elif source is None: + source = 0 + else: + raise TypeError("bad type for cast to %r: %r" % + (CTypesPrimitive, type(source).__name__)) + return source + # + kind1 = kind + class CTypesPrimitive(CTypesGenericPrimitive): + __slots__ = ['_value'] + _ctype = ctype + _reftypename = '%s &' % name + kind = kind1 + + def __init__(self, value): + self._value = value + + @staticmethod + def _create_ctype_obj(init): + if init is None: + return ctype() + return ctype(CTypesPrimitive._to_ctypes(init)) + + if kind == 'int' or kind == 'byte': + @classmethod + def _cast_from(cls, source): + source = _cast_source_to_int(source) + source = ctype(source).value # cast within range + return cls(source) + def __int__(self): + return self._value + + if kind == 'bool': + @classmethod + def _cast_from(cls, source): + if not isinstance(source, (int, long, float)): + source = _cast_source_to_int(source) + return cls(bool(source)) + def __int__(self): + return self._value + + if kind == 'char': + @classmethod + def _cast_from(cls, source): + source = _cast_source_to_int(source) + source = bytechr(source & 0xFF) + return cls(source) + def __int__(self): + return ord(self._value) + + if kind == 'float': + @classmethod + def _cast_from(cls, source): + if isinstance(source, float): + pass + elif isinstance(source, CTypesGenericPrimitive): + if hasattr(source, '__float__'): + source = float(source) + else: + source = int(source) + else: + source = _cast_source_to_int(source) + source = ctype(source).value # fix precision + return cls(source) + def __int__(self): + return int(self._value) + def __float__(self): + return self._value + + _cast_to_integer = __int__ + + if kind == 'int' or kind == 'byte' or kind == 'bool': + @staticmethod + def _to_ctypes(x): + if not isinstance(x, (int, long)): + if isinstance(x, CTypesData): + x = int(x) + else: + raise TypeError("integer expected, got %s" % + type(x).__name__) + if ctype(x).value != x: + if not is_signed and x < 0: + raise OverflowError("%s: negative integer" % name) + else: + raise OverflowError("%s: integer out of bounds" + % name) + return x + + if kind == 'char': + @staticmethod + def _to_ctypes(x): + if isinstance(x, bytes) and len(x) == 1: + return x + if isinstance(x, CTypesPrimitive): # > + return x._value + raise TypeError("character expected, got %s" % + type(x).__name__) + def __nonzero__(self): + return ord(self._value) != 0 + else: + def __nonzero__(self): + return self._value != 0 + __bool__ = __nonzero__ + + if kind == 'float': + @staticmethod + def _to_ctypes(x): + if not isinstance(x, (int, long, float, CTypesData)): + raise TypeError("float expected, got %s" % + type(x).__name__) + return ctype(x).value + + @staticmethod + def _from_ctypes(value): + return getattr(value, 'value', value) + + @staticmethod + def _initialize(blob, init): + blob.value = CTypesPrimitive._to_ctypes(init) + + if kind == 'char': + def _to_string(self, maxlen): + return self._value + if kind == 'byte': + def _to_string(self, maxlen): + return chr(self._value & 0xff) + # + CTypesPrimitive._fix_class() + return CTypesPrimitive + + def new_pointer_type(self, BItem): + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): + kind = 'charp' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'bytep' + elif BItem is getbtype(model.void_type): + kind = 'voidp' + else: + kind = 'generic' + # + class CTypesPtr(CTypesGenericPtr): + __slots__ = ['_own'] + if kind == 'charp': + __slots__ += ['__as_strbuf'] + _BItem = BItem + if hasattr(BItem, '_ctype'): + _ctype = ctypes.POINTER(BItem._ctype) + _bitem_size = ctypes.sizeof(BItem._ctype) + else: + _ctype = ctypes.c_void_p + if issubclass(BItem, CTypesGenericArray): + _reftypename = BItem._get_c_name('(* &)') + else: + _reftypename = BItem._get_c_name(' * &') + + def __init__(self, init): + ctypeobj = BItem._create_ctype_obj(init) + if kind == 'charp': + self.__as_strbuf = ctypes.create_string_buffer( + ctypeobj.value + b'\x00') + self._as_ctype_ptr = ctypes.cast( + self.__as_strbuf, self._ctype) + else: + self._as_ctype_ptr = ctypes.pointer(ctypeobj) + self._address = ctypes.cast(self._as_ctype_ptr, + ctypes.c_void_p).value + self._own = True + + def __add__(self, other): + if isinstance(other, (int, long)): + return self._new_pointer_at(self._address + + other * self._bitem_size) + else: + return NotImplemented + + def __sub__(self, other): + if isinstance(other, (int, long)): + return self._new_pointer_at(self._address - + other * self._bitem_size) + elif type(self) is type(other): + return (self._address - other._address) // self._bitem_size + else: + return NotImplemented + + def __getitem__(self, index): + if getattr(self, '_own', False) and index != 0: + raise IndexError + return BItem._from_ctypes(self._as_ctype_ptr[index]) + + def __setitem__(self, index, value): + self._as_ctype_ptr[index] = BItem._to_ctypes(value) + + if kind == 'charp' or kind == 'voidp': + @classmethod + def _arg_to_ctypes(cls, *value): + if value and isinstance(value[0], bytes): + return ctypes.c_char_p(value[0]) + else: + return super(CTypesPtr, cls)._arg_to_ctypes(*value) + + if kind == 'charp' or kind == 'bytep': + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = sys.maxsize + p = ctypes.cast(self._as_ctype_ptr, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != b'\x00': + n += 1 + return b''.join([p[i] for i in range(n)]) + + def _get_own_repr(self): + if getattr(self, '_own', False): + return 'owning %d bytes' % ( + ctypes.sizeof(self._as_ctype_ptr.contents),) + return super(CTypesPtr, self)._get_own_repr() + # + if (BItem is self.ffi._get_cached_btype(model.void_type) or + BItem is self.ffi._get_cached_btype(model.PrimitiveType('char'))): + CTypesPtr._automatic_casts = True + # + CTypesPtr._fix_class() + return CTypesPtr + + def new_array_type(self, CTypesPtr, length): + if length is None: + brackets = ' &[]' + else: + brackets = ' &[%d]' % length + BItem = CTypesPtr._BItem + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): + kind = 'char' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'byte' + else: + kind = 'generic' + # + class CTypesArray(CTypesGenericArray): + __slots__ = ['_blob', '_own'] + if length is not None: + _ctype = BItem._ctype * length + else: + __slots__.append('_ctype') + _reftypename = BItem._get_c_name(brackets) + _declared_length = length + _CTPtr = CTypesPtr + + def __init__(self, init): + if length is None: + if isinstance(init, (int, long)): + len1 = init + init = None + elif kind == 'char' and isinstance(init, bytes): + len1 = len(init) + 1 # extra null + else: + init = tuple(init) + len1 = len(init) + self._ctype = BItem._ctype * len1 + self._blob = self._ctype() + self._own = True + if init is not None: + self._initialize(self._blob, init) + + @staticmethod + def _initialize(blob, init): + if isinstance(init, bytes): + init = [init[i:i+1] for i in range(len(init))] + else: + if isinstance(init, CTypesGenericArray): + if (len(init) != len(blob) or + not isinstance(init, CTypesArray)): + raise TypeError("length/type mismatch: %s" % (init,)) + init = tuple(init) + if len(init) > len(blob): + raise IndexError("too many initializers") + addr = ctypes.cast(blob, ctypes.c_void_p).value + PTR = ctypes.POINTER(BItem._ctype) + itemsize = ctypes.sizeof(BItem._ctype) + for i, value in enumerate(init): + p = ctypes.cast(addr + i * itemsize, PTR) + BItem._initialize(p.contents, value) + + def __len__(self): + return len(self._blob) + + def __getitem__(self, index): + if not (0 <= index < len(self._blob)): + raise IndexError + return BItem._from_ctypes(self._blob[index]) + + def __setitem__(self, index, value): + if not (0 <= index < len(self._blob)): + raise IndexError + self._blob[index] = BItem._to_ctypes(value) + + if kind == 'char' or kind == 'byte': + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = len(self._blob) + p = ctypes.cast(self._blob, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != b'\x00': + n += 1 + return b''.join([p[i] for i in range(n)]) + + def _get_own_repr(self): + if getattr(self, '_own', False): + return 'owning %d bytes' % (ctypes.sizeof(self._blob),) + return super(CTypesArray, self)._get_own_repr() + + def _convert_to_address(self, BClass): + if BClass in (CTypesPtr, None) or BClass._automatic_casts: + return ctypes.addressof(self._blob) + else: + return CTypesData._convert_to_address(self, BClass) + + @staticmethod + def _from_ctypes(ctypes_array): + self = CTypesArray.__new__(CTypesArray) + self._blob = ctypes_array + return self + + @staticmethod + def _arg_to_ctypes(value): + return CTypesPtr._arg_to_ctypes(value) + + def __add__(self, other): + if isinstance(other, (int, long)): + return CTypesPtr._new_pointer_at( + ctypes.addressof(self._blob) + + other * ctypes.sizeof(BItem._ctype)) + else: + return NotImplemented + + @classmethod + def _cast_from(cls, source): + raise NotImplementedError("casting to %r" % ( + cls._get_c_name(),)) + # + CTypesArray._fix_class() + return CTypesArray + + def _new_struct_or_union(self, kind, name, base_ctypes_class): + # + class struct_or_union(base_ctypes_class): + pass + struct_or_union.__name__ = '%s_%s' % (kind, name) + kind1 = kind + # + class CTypesStructOrUnion(CTypesBaseStructOrUnion): + __slots__ = ['_blob'] + _ctype = struct_or_union + _reftypename = '%s &' % (name,) + _kind = kind = kind1 + # + CTypesStructOrUnion._fix_class() + return CTypesStructOrUnion + + def new_struct_type(self, name): + return self._new_struct_or_union('struct', name, ctypes.Structure) + + def new_union_type(self, name): + return self._new_struct_or_union('union', name, ctypes.Union) + + def complete_struct_or_union(self, CTypesStructOrUnion, fields, tp, + totalsize=-1, totalalignment=-1, sflags=0, + pack=0): + if totalsize >= 0 or totalalignment >= 0: + raise NotImplementedError("the ctypes backend of CFFI does not support " + "structures completed by verify(); please " + "compile and install the _cffi_backend module.") + struct_or_union = CTypesStructOrUnion._ctype + fnames = [fname for (fname, BField, bitsize) in fields] + btypes = [BField for (fname, BField, bitsize) in fields] + bitfields = [bitsize for (fname, BField, bitsize) in fields] + # + bfield_types = {} + cfields = [] + for (fname, BField, bitsize) in fields: + if bitsize < 0: + cfields.append((fname, BField._ctype)) + bfield_types[fname] = BField + else: + cfields.append((fname, BField._ctype, bitsize)) + bfield_types[fname] = Ellipsis + if sflags & 8: + struct_or_union._pack_ = 1 + elif pack: + struct_or_union._pack_ = pack + struct_or_union._fields_ = cfields + CTypesStructOrUnion._bfield_types = bfield_types + # + @staticmethod + def _create_ctype_obj(init): + result = struct_or_union() + if init is not None: + initialize(result, init) + return result + CTypesStructOrUnion._create_ctype_obj = _create_ctype_obj + # + def initialize(blob, init): + if is_union: + if len(init) > 1: + raise ValueError("union initializer: %d items given, but " + "only one supported (use a dict if needed)" + % (len(init),)) + if not isinstance(init, dict): + if isinstance(init, (bytes, unicode)): + raise TypeError("union initializer: got a str") + init = tuple(init) + if len(init) > len(fnames): + raise ValueError("too many values for %s initializer" % + CTypesStructOrUnion._get_c_name()) + init = dict(zip(fnames, init)) + addr = ctypes.addressof(blob) + for fname, value in init.items(): + BField, bitsize = name2fieldtype[fname] + assert bitsize < 0, \ + "not implemented: initializer with bit fields" + offset = CTypesStructOrUnion._offsetof(fname) + PTR = ctypes.POINTER(BField._ctype) + p = ctypes.cast(addr + offset, PTR) + BField._initialize(p.contents, value) + is_union = CTypesStructOrUnion._kind == 'union' + name2fieldtype = dict(zip(fnames, zip(btypes, bitfields))) + # + for fname, BField, bitsize in fields: + if fname == '': + raise NotImplementedError("nested anonymous structs/unions") + if hasattr(CTypesStructOrUnion, fname): + raise ValueError("the field name %r conflicts in " + "the ctypes backend" % fname) + if bitsize < 0: + def getter(self, fname=fname, BField=BField, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BField._from_ctypes(p.contents) + def setter(self, value, fname=fname, BField=BField): + setattr(self._blob, fname, BField._to_ctypes(value)) + # + if issubclass(BField, CTypesGenericArray): + setter = None + if BField._declared_length == 0: + def getter(self, fname=fname, BFieldPtr=BField._CTPtr, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BFieldPtr._from_ctypes(p) + # + else: + def getter(self, fname=fname, BField=BField): + return BField._from_ctypes(getattr(self._blob, fname)) + def setter(self, value, fname=fname, BField=BField): + # xxx obscure workaround + value = BField._to_ctypes(value) + oldvalue = getattr(self._blob, fname) + setattr(self._blob, fname, value) + if value != getattr(self._blob, fname): + setattr(self._blob, fname, oldvalue) + raise OverflowError("value too large for bitfield") + setattr(CTypesStructOrUnion, fname, property(getter, setter)) + # + CTypesPtr = self.ffi._get_cached_btype(model.PointerType(tp)) + for fname in fnames: + if hasattr(CTypesPtr, fname): + raise ValueError("the field name %r conflicts in " + "the ctypes backend" % fname) + def getter(self, fname=fname): + return getattr(self[0], fname) + def setter(self, value, fname=fname): + setattr(self[0], fname, value) + setattr(CTypesPtr, fname, property(getter, setter)) + + def new_function_type(self, BArgs, BResult, has_varargs): + nameargs = [BArg._get_c_name() for BArg in BArgs] + if has_varargs: + nameargs.append('...') + nameargs = ', '.join(nameargs) + # + class CTypesFunctionPtr(CTypesGenericPtr): + __slots__ = ['_own_callback', '_name'] + _ctype = ctypes.CFUNCTYPE(getattr(BResult, '_ctype', None), + *[BArg._ctype for BArg in BArgs], + use_errno=True) + _reftypename = BResult._get_c_name('(* &)(%s)' % (nameargs,)) + + def __init__(self, init, error=None): + # create a callback to the Python callable init() + import traceback + assert not has_varargs, "varargs not supported for callbacks" + if getattr(BResult, '_ctype', None) is not None: + error = BResult._from_ctypes( + BResult._create_ctype_obj(error)) + else: + error = None + def callback(*args): + args2 = [] + for arg, BArg in zip(args, BArgs): + args2.append(BArg._from_ctypes(arg)) + try: + res2 = init(*args2) + res2 = BResult._to_ctypes(res2) + except: + traceback.print_exc() + res2 = error + if issubclass(BResult, CTypesGenericPtr): + if res2: + res2 = ctypes.cast(res2, ctypes.c_void_p).value + # .value: http://bugs.python.org/issue1574593 + else: + res2 = None + #print repr(res2) + return res2 + if issubclass(BResult, CTypesGenericPtr): + # The only pointers callbacks can return are void*s: + # http://bugs.python.org/issue5710 + callback_ctype = ctypes.CFUNCTYPE( + ctypes.c_void_p, + *[BArg._ctype for BArg in BArgs], + use_errno=True) + else: + callback_ctype = CTypesFunctionPtr._ctype + self._as_ctype_ptr = callback_ctype(callback) + self._address = ctypes.cast(self._as_ctype_ptr, + ctypes.c_void_p).value + self._own_callback = init + + @staticmethod + def _initialize(ctypes_ptr, value): + if value: + raise NotImplementedError("ctypes backend: not supported: " + "initializers for function pointers") + + def __repr__(self): + c_name = getattr(self, '_name', None) + if c_name: + i = self._reftypename.index('(* &)') + if self._reftypename[i-1] not in ' )*': + c_name = ' ' + c_name + c_name = self._reftypename.replace('(* &)', c_name) + return CTypesData.__repr__(self, c_name) + + def _get_own_repr(self): + if getattr(self, '_own_callback', None) is not None: + return 'calling %r' % (self._own_callback,) + return super(CTypesFunctionPtr, self)._get_own_repr() + + def __call__(self, *args): + if has_varargs: + assert len(args) >= len(BArgs) + extraargs = args[len(BArgs):] + args = args[:len(BArgs)] + else: + assert len(args) == len(BArgs) + ctypes_args = [] + for arg, BArg in zip(args, BArgs): + ctypes_args.append(BArg._arg_to_ctypes(arg)) + if has_varargs: + for i, arg in enumerate(extraargs): + if arg is None: + ctypes_args.append(ctypes.c_void_p(0)) # NULL + continue + if not isinstance(arg, CTypesData): + raise TypeError( + "argument %d passed in the variadic part " + "needs to be a cdata object (got %s)" % + (1 + len(BArgs) + i, type(arg).__name__)) + ctypes_args.append(arg._arg_to_ctypes(arg)) + result = self._as_ctype_ptr(*ctypes_args) + return BResult._from_ctypes(result) + # + CTypesFunctionPtr._fix_class() + return CTypesFunctionPtr + + def new_enum_type(self, name, enumerators, enumvalues, CTypesInt): + assert isinstance(name, str) + reverse_mapping = dict(zip(reversed(enumvalues), + reversed(enumerators))) + # + class CTypesEnum(CTypesInt): + __slots__ = [] + _reftypename = '%s &' % name + + def _get_own_repr(self): + value = self._value + try: + return '%d: %s' % (value, reverse_mapping[value]) + except KeyError: + return str(value) + + def _to_string(self, maxlen): + value = self._value + try: + return reverse_mapping[value] + except KeyError: + return str(value) + # + CTypesEnum._fix_class() + return CTypesEnum + + def get_errno(self): + return ctypes.get_errno() + + def set_errno(self, value): + ctypes.set_errno(value) + + def string(self, b, maxlen=-1): + return b._to_string(maxlen) + + def buffer(self, bptr, size=-1): + raise NotImplementedError("buffer() with ctypes backend") + + def sizeof(self, cdata_or_BType): + if isinstance(cdata_or_BType, CTypesData): + return cdata_or_BType._get_size_of_instance() + else: + assert issubclass(cdata_or_BType, CTypesData) + return cdata_or_BType._get_size() + + def alignof(self, BType): + assert issubclass(BType, CTypesData) + return BType._alignment() + + def newp(self, BType, source): + if not issubclass(BType, CTypesData): + raise TypeError + return BType._newp(source) + + def cast(self, BType, source): + return BType._cast_from(source) + + def callback(self, BType, source, error, onerror): + assert onerror is None # XXX not implemented + return BType(source, error) + + _weakref_cache_ref = None + + def gcp(self, cdata, destructor, size=0): + if self._weakref_cache_ref is None: + import weakref + class MyRef(weakref.ref): + def __eq__(self, other): + myref = self() + return self is other or ( + myref is not None and myref is other()) + def __ne__(self, other): + return not (self == other) + def __hash__(self): + try: + return self._hash + except AttributeError: + self._hash = hash(self()) + return self._hash + self._weakref_cache_ref = {}, MyRef + weak_cache, MyRef = self._weakref_cache_ref + + if destructor is None: + try: + del weak_cache[MyRef(cdata)] + except KeyError: + raise TypeError("Can remove destructor only on a object " + "previously returned by ffi.gc()") + return None + + def remove(k): + cdata, destructor = weak_cache.pop(k, (None, None)) + if destructor is not None: + destructor(cdata) + + new_cdata = self.cast(self.typeof(cdata), cdata) + assert new_cdata is not cdata + weak_cache[MyRef(new_cdata, remove)] = (cdata, destructor) + return new_cdata + + typeof = type + + def getcname(self, BType, replace_with): + return BType._get_c_name(replace_with) + + def typeoffsetof(self, BType, fieldname, num=0): + if isinstance(fieldname, str): + if num == 0 and issubclass(BType, CTypesGenericPtr): + BType = BType._BItem + if not issubclass(BType, CTypesBaseStructOrUnion): + raise TypeError("expected a struct or union ctype") + BField = BType._bfield_types[fieldname] + if BField is Ellipsis: + raise TypeError("not supported for bitfields") + return (BField, BType._offsetof(fieldname)) + elif isinstance(fieldname, (int, long)): + if issubclass(BType, CTypesGenericArray): + BType = BType._CTPtr + if not issubclass(BType, CTypesGenericPtr): + raise TypeError("expected an array or ptr ctype") + BItem = BType._BItem + offset = BItem._get_size() * fieldname + if offset > sys.maxsize: + raise OverflowError + return (BItem, offset) + else: + raise TypeError(type(fieldname)) + + def rawaddressof(self, BTypePtr, cdata, offset=None): + if isinstance(cdata, CTypesBaseStructOrUnion): + ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata)) + elif isinstance(cdata, CTypesGenericPtr): + if offset is None or not issubclass(type(cdata)._BItem, + CTypesBaseStructOrUnion): + raise TypeError("unexpected cdata type") + ptr = type(cdata)._to_ctypes(cdata) + elif isinstance(cdata, CTypesGenericArray): + ptr = type(cdata)._to_ctypes(cdata) + else: + raise TypeError("expected a ") + if offset: + ptr = ctypes.cast( + ctypes.c_void_p( + ctypes.cast(ptr, ctypes.c_void_p).value + offset), + type(ptr)) + return BTypePtr._from_ctypes(ptr) + + +class CTypesLibrary(object): + + def __init__(self, backend, cdll): + self.backend = backend + self.cdll = cdll + + def load_function(self, BType, name): + c_func = getattr(self.cdll, name) + funcobj = BType._from_ctypes(c_func) + funcobj._name = name + return funcobj + + def read_variable(self, BType, name): + try: + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + except AttributeError as e: + raise NotImplementedError(e) + return BType._from_ctypes(ctypes_obj) + + def write_variable(self, BType, name, value): + new_ctypes_obj = BType._to_ctypes(value) + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + ctypes.memmove(ctypes.addressof(ctypes_obj), + ctypes.addressof(new_ctypes_obj), + ctypes.sizeof(BType._ctype)) diff --git a/cffi/cffi_opcode.py b/cffi/cffi_opcode.py new file mode 100644 index 0000000..a0df98d --- /dev/null +++ b/cffi/cffi_opcode.py @@ -0,0 +1,187 @@ +from .error import VerificationError + +class CffiOp(object): + def __init__(self, op, arg): + self.op = op + self.arg = arg + + def as_c_expr(self): + if self.op is None: + assert isinstance(self.arg, str) + return '(_cffi_opcode_t)(%s)' % (self.arg,) + classname = CLASS_NAME[self.op] + return '_CFFI_OP(_CFFI_OP_%s, %s)' % (classname, self.arg) + + def as_python_bytes(self): + if self.op is None and self.arg.isdigit(): + value = int(self.arg) # non-negative: '-' not in self.arg + if value >= 2**31: + raise OverflowError("cannot emit %r: limited to 2**31-1" + % (self.arg,)) + return format_four_bytes(value) + if isinstance(self.arg, str): + raise VerificationError("cannot emit to Python: %r" % (self.arg,)) + return format_four_bytes((self.arg << 8) | self.op) + + def __str__(self): + classname = CLASS_NAME.get(self.op, self.op) + return '(%s %s)' % (classname, self.arg) + +def format_four_bytes(num): + return '\\x%02X\\x%02X\\x%02X\\x%02X' % ( + (num >> 24) & 0xFF, + (num >> 16) & 0xFF, + (num >> 8) & 0xFF, + (num ) & 0xFF) + +OP_PRIMITIVE = 1 +OP_POINTER = 3 +OP_ARRAY = 5 +OP_OPEN_ARRAY = 7 +OP_STRUCT_UNION = 9 +OP_ENUM = 11 +OP_FUNCTION = 13 +OP_FUNCTION_END = 15 +OP_NOOP = 17 +OP_BITFIELD = 19 +OP_TYPENAME = 21 +OP_CPYTHON_BLTN_V = 23 # varargs +OP_CPYTHON_BLTN_N = 25 # noargs +OP_CPYTHON_BLTN_O = 27 # O (i.e. a single arg) +OP_CONSTANT = 29 +OP_CONSTANT_INT = 31 +OP_GLOBAL_VAR = 33 +OP_DLOPEN_FUNC = 35 +OP_DLOPEN_CONST = 37 +OP_GLOBAL_VAR_F = 39 +OP_EXTERN_PYTHON = 41 + +PRIM_VOID = 0 +PRIM_BOOL = 1 +PRIM_CHAR = 2 +PRIM_SCHAR = 3 +PRIM_UCHAR = 4 +PRIM_SHORT = 5 +PRIM_USHORT = 6 +PRIM_INT = 7 +PRIM_UINT = 8 +PRIM_LONG = 9 +PRIM_ULONG = 10 +PRIM_LONGLONG = 11 +PRIM_ULONGLONG = 12 +PRIM_FLOAT = 13 +PRIM_DOUBLE = 14 +PRIM_LONGDOUBLE = 15 + +PRIM_WCHAR = 16 +PRIM_INT8 = 17 +PRIM_UINT8 = 18 +PRIM_INT16 = 19 +PRIM_UINT16 = 20 +PRIM_INT32 = 21 +PRIM_UINT32 = 22 +PRIM_INT64 = 23 +PRIM_UINT64 = 24 +PRIM_INTPTR = 25 +PRIM_UINTPTR = 26 +PRIM_PTRDIFF = 27 +PRIM_SIZE = 28 +PRIM_SSIZE = 29 +PRIM_INT_LEAST8 = 30 +PRIM_UINT_LEAST8 = 31 +PRIM_INT_LEAST16 = 32 +PRIM_UINT_LEAST16 = 33 +PRIM_INT_LEAST32 = 34 +PRIM_UINT_LEAST32 = 35 +PRIM_INT_LEAST64 = 36 +PRIM_UINT_LEAST64 = 37 +PRIM_INT_FAST8 = 38 +PRIM_UINT_FAST8 = 39 +PRIM_INT_FAST16 = 40 +PRIM_UINT_FAST16 = 41 +PRIM_INT_FAST32 = 42 +PRIM_UINT_FAST32 = 43 +PRIM_INT_FAST64 = 44 +PRIM_UINT_FAST64 = 45 +PRIM_INTMAX = 46 +PRIM_UINTMAX = 47 +PRIM_FLOATCOMPLEX = 48 +PRIM_DOUBLECOMPLEX = 49 +PRIM_CHAR16 = 50 +PRIM_CHAR32 = 51 + +_NUM_PRIM = 52 +_UNKNOWN_PRIM = -1 +_UNKNOWN_FLOAT_PRIM = -2 +_UNKNOWN_LONG_DOUBLE = -3 + +_IO_FILE_STRUCT = -1 + +PRIMITIVE_TO_INDEX = { + 'char': PRIM_CHAR, + 'short': PRIM_SHORT, + 'int': PRIM_INT, + 'long': PRIM_LONG, + 'long long': PRIM_LONGLONG, + 'signed char': PRIM_SCHAR, + 'unsigned char': PRIM_UCHAR, + 'unsigned short': PRIM_USHORT, + 'unsigned int': PRIM_UINT, + 'unsigned long': PRIM_ULONG, + 'unsigned long long': PRIM_ULONGLONG, + 'float': PRIM_FLOAT, + 'double': PRIM_DOUBLE, + 'long double': PRIM_LONGDOUBLE, + 'float _Complex': PRIM_FLOATCOMPLEX, + 'double _Complex': PRIM_DOUBLECOMPLEX, + '_Bool': PRIM_BOOL, + 'wchar_t': PRIM_WCHAR, + 'char16_t': PRIM_CHAR16, + 'char32_t': PRIM_CHAR32, + 'int8_t': PRIM_INT8, + 'uint8_t': PRIM_UINT8, + 'int16_t': PRIM_INT16, + 'uint16_t': PRIM_UINT16, + 'int32_t': PRIM_INT32, + 'uint32_t': PRIM_UINT32, + 'int64_t': PRIM_INT64, + 'uint64_t': PRIM_UINT64, + 'intptr_t': PRIM_INTPTR, + 'uintptr_t': PRIM_UINTPTR, + 'ptrdiff_t': PRIM_PTRDIFF, + 'size_t': PRIM_SIZE, + 'ssize_t': PRIM_SSIZE, + 'int_least8_t': PRIM_INT_LEAST8, + 'uint_least8_t': PRIM_UINT_LEAST8, + 'int_least16_t': PRIM_INT_LEAST16, + 'uint_least16_t': PRIM_UINT_LEAST16, + 'int_least32_t': PRIM_INT_LEAST32, + 'uint_least32_t': PRIM_UINT_LEAST32, + 'int_least64_t': PRIM_INT_LEAST64, + 'uint_least64_t': PRIM_UINT_LEAST64, + 'int_fast8_t': PRIM_INT_FAST8, + 'uint_fast8_t': PRIM_UINT_FAST8, + 'int_fast16_t': PRIM_INT_FAST16, + 'uint_fast16_t': PRIM_UINT_FAST16, + 'int_fast32_t': PRIM_INT_FAST32, + 'uint_fast32_t': PRIM_UINT_FAST32, + 'int_fast64_t': PRIM_INT_FAST64, + 'uint_fast64_t': PRIM_UINT_FAST64, + 'intmax_t': PRIM_INTMAX, + 'uintmax_t': PRIM_UINTMAX, + } + +F_UNION = 0x01 +F_CHECK_FIELDS = 0x02 +F_PACKED = 0x04 +F_EXTERNAL = 0x08 +F_OPAQUE = 0x10 + +G_FLAGS = dict([('_CFFI_' + _key, globals()[_key]) + for _key in ['F_UNION', 'F_CHECK_FIELDS', 'F_PACKED', + 'F_EXTERNAL', 'F_OPAQUE']]) + +CLASS_NAME = {} +for _name, _value in list(globals().items()): + if _name.startswith('OP_') and isinstance(_value, int): + CLASS_NAME[_value] = _name[3:] diff --git a/cffi/commontypes.py b/cffi/commontypes.py new file mode 100644 index 0000000..8ec97c7 --- /dev/null +++ b/cffi/commontypes.py @@ -0,0 +1,80 @@ +import sys +from . import model +from .error import FFIError + + +COMMON_TYPES = {} + +try: + # fetch "bool" and all simple Windows types + from _cffi_backend import _get_common_types + _get_common_types(COMMON_TYPES) +except ImportError: + pass + +COMMON_TYPES['FILE'] = model.unknown_type('FILE', '_IO_FILE') +COMMON_TYPES['bool'] = '_Bool' # in case we got ImportError above + +for _type in model.PrimitiveType.ALL_PRIMITIVE_TYPES: + if _type.endswith('_t'): + COMMON_TYPES[_type] = _type +del _type + +_CACHE = {} + +def resolve_common_type(parser, commontype): + try: + return _CACHE[commontype] + except KeyError: + cdecl = COMMON_TYPES.get(commontype, commontype) + if not isinstance(cdecl, str): + result, quals = cdecl, 0 # cdecl is already a BaseType + elif cdecl in model.PrimitiveType.ALL_PRIMITIVE_TYPES: + result, quals = model.PrimitiveType(cdecl), 0 + elif cdecl == 'set-unicode-needed': + raise FFIError("The Windows type %r is only available after " + "you call ffi.set_unicode()" % (commontype,)) + else: + if commontype == cdecl: + raise FFIError( + "Unsupported type: %r. Please look at " + "http://cffi.readthedocs.io/en/latest/cdef.html#ffi-cdef-limitations " + "and file an issue if you think this type should really " + "be supported." % (commontype,)) + result, quals = parser.parse_type_and_quals(cdecl) # recursive + + assert isinstance(result, model.BaseTypeByIdentity) + _CACHE[commontype] = result, quals + return result, quals + + +# ____________________________________________________________ +# extra types for Windows (most of them are in commontypes.c) + + +def win_common_types(): + return { + "UNICODE_STRING": model.StructType( + "_UNICODE_STRING", + ["Length", + "MaximumLength", + "Buffer"], + [model.PrimitiveType("unsigned short"), + model.PrimitiveType("unsigned short"), + model.PointerType(model.PrimitiveType("wchar_t"))], + [-1, -1, -1]), + "PUNICODE_STRING": "UNICODE_STRING *", + "PCUNICODE_STRING": "const UNICODE_STRING *", + + "TBYTE": "set-unicode-needed", + "TCHAR": "set-unicode-needed", + "LPCTSTR": "set-unicode-needed", + "PCTSTR": "set-unicode-needed", + "LPTSTR": "set-unicode-needed", + "PTSTR": "set-unicode-needed", + "PTBYTE": "set-unicode-needed", + "PTCHAR": "set-unicode-needed", + } + +if sys.platform == 'win32': + COMMON_TYPES.update(win_common_types()) diff --git a/cffi/cparser.py b/cffi/cparser.py new file mode 100644 index 0000000..df6303d --- /dev/null +++ b/cffi/cparser.py @@ -0,0 +1,923 @@ +from . import model +from .commontypes import COMMON_TYPES, resolve_common_type +from .error import FFIError, CDefError +try: + from . import _pycparser as pycparser +except ImportError: + import pycparser +import weakref, re, sys + +try: + if sys.version_info < (3,): + import thread as _thread + else: + import _thread + lock = _thread.allocate_lock() +except ImportError: + lock = None + +def _workaround_for_static_import_finders(): + # Issue #392: packaging tools like cx_Freeze can not find these + # because pycparser uses exec dynamic import. This is an obscure + # workaround. This function is never called. + import pycparser.yacctab + import pycparser.lextab + +CDEF_SOURCE_STRING = "" +_r_comment = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", + re.DOTALL | re.MULTILINE) +_r_define = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)" + r"\b((?:[^\n\\]|\\.)*?)$", + re.DOTALL | re.MULTILINE) +_r_partial_enum = re.compile(r"=\s*\.\.\.\s*[,}]|\.\.\.\s*\}") +_r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$") +_r_partial_array = re.compile(r"\[\s*\.\.\.\s*\]") +_r_words = re.compile(r"\w+|\S") +_parser_cache = None +_r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE) +_r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b") +_r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b") +_r_cdecl = re.compile(r"\b__cdecl\b") +_r_extern_python = re.compile(r'\bextern\s*"' + r'(Python|Python\s*\+\s*C|C\s*\+\s*Python)"\s*.') +_r_star_const_space = re.compile( # matches "* const " + r"[*]\s*((const|volatile|restrict)\b\s*)+") +_r_int_dotdotdot = re.compile(r"(\b(int|long|short|signed|unsigned|char)\s*)+" + r"\.\.\.") +_r_float_dotdotdot = re.compile(r"\b(double|float)\s*\.\.\.") + +def _get_parser(): + global _parser_cache + if _parser_cache is None: + _parser_cache = pycparser.CParser() + return _parser_cache + +def _workaround_for_old_pycparser(csource): + # Workaround for a pycparser issue (fixed between pycparser 2.10 and + # 2.14): "char*const***" gives us a wrong syntax tree, the same as + # for "char***(*const)". This means we can't tell the difference + # afterwards. But "char(*const(***))" gives us the right syntax + # tree. The issue only occurs if there are several stars in + # sequence with no parenthesis inbetween, just possibly qualifiers. + # Attempt to fix it by adding some parentheses in the source: each + # time we see "* const" or "* const *", we add an opening + # parenthesis before each star---the hard part is figuring out where + # to close them. + parts = [] + while True: + match = _r_star_const_space.search(csource) + if not match: + break + #print repr(''.join(parts)+csource), '=>', + parts.append(csource[:match.start()]) + parts.append('('); closing = ')' + parts.append(match.group()) # e.g. "* const " + endpos = match.end() + if csource.startswith('*', endpos): + parts.append('('); closing += ')' + level = 0 + i = endpos + while i < len(csource): + c = csource[i] + if c == '(': + level += 1 + elif c == ')': + if level == 0: + break + level -= 1 + elif c in ',;=': + if level == 0: + break + i += 1 + csource = csource[endpos:i] + closing + csource[i:] + #print repr(''.join(parts)+csource) + parts.append(csource) + return ''.join(parts) + +def _preprocess_extern_python(csource): + # input: `extern "Python" int foo(int);` or + # `extern "Python" { int foo(int); }` + # output: + # void __cffi_extern_python_start; + # int foo(int); + # void __cffi_extern_python_stop; + # + # input: `extern "Python+C" int foo(int);` + # output: + # void __cffi_extern_python_plus_c_start; + # int foo(int); + # void __cffi_extern_python_stop; + parts = [] + while True: + match = _r_extern_python.search(csource) + if not match: + break + endpos = match.end() - 1 + #print + #print ''.join(parts)+csource + #print '=>' + parts.append(csource[:match.start()]) + if 'C' in match.group(1): + parts.append('void __cffi_extern_python_plus_c_start; ') + else: + parts.append('void __cffi_extern_python_start; ') + if csource[endpos] == '{': + # grouping variant + closing = csource.find('}', endpos) + if closing < 0: + raise CDefError("'extern \"Python\" {': no '}' found") + if csource.find('{', endpos + 1, closing) >= 0: + raise NotImplementedError("cannot use { } inside a block " + "'extern \"Python\" { ... }'") + parts.append(csource[endpos+1:closing]) + csource = csource[closing+1:] + else: + # non-grouping variant + semicolon = csource.find(';', endpos) + if semicolon < 0: + raise CDefError("'extern \"Python\": no ';' found") + parts.append(csource[endpos:semicolon+1]) + csource = csource[semicolon+1:] + parts.append(' void __cffi_extern_python_stop;') + #print ''.join(parts)+csource + #print + parts.append(csource) + return ''.join(parts) + +def _warn_for_string_literal(csource): + if '"' in csource: + import warnings + warnings.warn("String literal found in cdef() or type source. " + "String literals are ignored here, but you should " + "remove them anyway because some character sequences " + "confuse pre-parsing.") + +def _preprocess(csource): + # Remove comments. NOTE: this only work because the cdef() section + # should not contain any string literal! + csource = _r_comment.sub(' ', csource) + # Remove the "#define FOO x" lines + macros = {} + for match in _r_define.finditer(csource): + macroname, macrovalue = match.groups() + macrovalue = macrovalue.replace('\\\n', '').strip() + macros[macroname] = macrovalue + csource = _r_define.sub('', csource) + # + if pycparser.__version__ < '2.14': + csource = _workaround_for_old_pycparser(csource) + # + # BIG HACK: replace WINAPI or __stdcall with "volatile const". + # It doesn't make sense for the return type of a function to be + # "volatile volatile const", so we abuse it to detect __stdcall... + # Hack number 2 is that "int(volatile *fptr)();" is not valid C + # syntax, so we place the "volatile" before the opening parenthesis. + csource = _r_stdcall2.sub(' volatile volatile const(', csource) + csource = _r_stdcall1.sub(' volatile volatile const ', csource) + csource = _r_cdecl.sub(' ', csource) + # + # Replace `extern "Python"` with start/end markers + csource = _preprocess_extern_python(csource) + # + # Now there should not be any string literal left; warn if we get one + _warn_for_string_literal(csource) + # + # Replace "[...]" with "[__dotdotdotarray__]" + csource = _r_partial_array.sub('[__dotdotdotarray__]', csource) + # + # Replace "...}" with "__dotdotdotNUM__}". This construction should + # occur only at the end of enums; at the end of structs we have "...;}" + # and at the end of vararg functions "...);". Also replace "=...[,}]" + # with ",__dotdotdotNUM__[,}]": this occurs in the enums too, when + # giving an unknown value. + matches = list(_r_partial_enum.finditer(csource)) + for number, match in enumerate(reversed(matches)): + p = match.start() + if csource[p] == '=': + p2 = csource.find('...', p, match.end()) + assert p2 > p + csource = '%s,__dotdotdot%d__ %s' % (csource[:p], number, + csource[p2+3:]) + else: + assert csource[p:p+3] == '...' + csource = '%s __dotdotdot%d__ %s' % (csource[:p], number, + csource[p+3:]) + # Replace "int ..." or "unsigned long int..." with "__dotdotdotint__" + csource = _r_int_dotdotdot.sub(' __dotdotdotint__ ', csource) + # Replace "float ..." or "double..." with "__dotdotdotfloat__" + csource = _r_float_dotdotdot.sub(' __dotdotdotfloat__ ', csource) + # Replace all remaining "..." with the same name, "__dotdotdot__", + # which is declared with a typedef for the purpose of C parsing. + return csource.replace('...', ' __dotdotdot__ '), macros + +def _common_type_names(csource): + # Look in the source for what looks like usages of types from the + # list of common types. A "usage" is approximated here as the + # appearance of the word, minus a "definition" of the type, which + # is the last word in a "typedef" statement. Approximative only + # but should be fine for all the common types. + look_for_words = set(COMMON_TYPES) + look_for_words.add(';') + look_for_words.add(',') + look_for_words.add('(') + look_for_words.add(')') + look_for_words.add('typedef') + words_used = set() + is_typedef = False + paren = 0 + previous_word = '' + for word in _r_words.findall(csource): + if word in look_for_words: + if word == ';': + if is_typedef: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + is_typedef = False + elif word == 'typedef': + is_typedef = True + paren = 0 + elif word == '(': + paren += 1 + elif word == ')': + paren -= 1 + elif word == ',': + if is_typedef and paren == 0: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + else: # word in COMMON_TYPES + words_used.add(word) + previous_word = word + return words_used + + +class Parser(object): + + def __init__(self): + self._declarations = {} + self._included_declarations = set() + self._anonymous_counter = 0 + self._structnode2type = weakref.WeakKeyDictionary() + self._options = {} + self._int_constants = {} + self._recomplete = [] + self._uses_new_feature = None + + def _parse(self, csource): + csource, macros = _preprocess(csource) + # XXX: for more efficiency we would need to poke into the + # internals of CParser... the following registers the + # typedefs, because their presence or absence influences the + # parsing itself (but what they are typedef'ed to plays no role) + ctn = _common_type_names(csource) + typenames = [] + for name in sorted(self._declarations): + if name.startswith('typedef '): + name = name[8:] + typenames.append(name) + ctn.discard(name) + typenames += sorted(ctn) + # + csourcelines = [] + csourcelines.append('# 1 ""') + for typename in typenames: + csourcelines.append('typedef int %s;' % typename) + csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,' + ' __dotdotdot__;') + # this forces pycparser to consider the following in the file + # called from line 1 + csourcelines.append('# 1 "%s"' % (CDEF_SOURCE_STRING,)) + csourcelines.append(csource) + fullcsource = '\n'.join(csourcelines) + if lock is not None: + lock.acquire() # pycparser is not thread-safe... + try: + ast = _get_parser().parse(fullcsource) + except pycparser.c_parser.ParseError as e: + self.convert_pycparser_error(e, csource) + finally: + if lock is not None: + lock.release() + # csource will be used to find buggy source text + return ast, macros, csource + + def _convert_pycparser_error(self, e, csource): + # xxx look for ":NUM:" at the start of str(e) + # and interpret that as a line number. This will not work if + # the user gives explicit ``# NUM "FILE"`` directives. + line = None + msg = str(e) + match = re.match(r"%s:(\d+):" % (CDEF_SOURCE_STRING,), msg) + if match: + linenum = int(match.group(1), 10) + csourcelines = csource.splitlines() + if 1 <= linenum <= len(csourcelines): + line = csourcelines[linenum-1] + return line + + def convert_pycparser_error(self, e, csource): + line = self._convert_pycparser_error(e, csource) + + msg = str(e) + if line: + msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) + else: + msg = 'parse error\n%s' % (msg,) + raise CDefError(msg) + + def parse(self, csource, override=False, packed=False, pack=None, + dllexport=False): + if packed: + if packed != True: + raise ValueError("'packed' should be False or True; use " + "'pack' to give another value") + if pack: + raise ValueError("cannot give both 'pack' and 'packed'") + pack = 1 + elif pack: + if pack & (pack - 1): + raise ValueError("'pack' must be a power of two, not %r" % + (pack,)) + else: + pack = 0 + prev_options = self._options + try: + self._options = {'override': override, + 'packed': pack, + 'dllexport': dllexport} + self._internal_parse(csource) + finally: + self._options = prev_options + + def _internal_parse(self, csource): + ast, macros, csource = self._parse(csource) + # add the macros + self._process_macros(macros) + # find the first "__dotdotdot__" and use that as a separator + # between the repeated typedefs and the real csource + iterator = iter(ast.ext) + for decl in iterator: + if decl.name == '__dotdotdot__': + break + else: + assert 0 + current_decl = None + # + try: + self._inside_extern_python = '__cffi_extern_python_stop' + for decl in iterator: + current_decl = decl + if isinstance(decl, pycparser.c_ast.Decl): + self._parse_decl(decl) + elif isinstance(decl, pycparser.c_ast.Typedef): + if not decl.name: + raise CDefError("typedef does not declare any name", + decl) + quals = 0 + if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) and + decl.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_type(decl) + elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and + isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and + isinstance(decl.type.type.type, + pycparser.c_ast.IdentifierType) and + decl.type.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_ptr_type(decl) + else: + realtype, quals = self._get_type_and_quals( + decl.type, name=decl.name, partial_length_ok=True) + self._declare('typedef ' + decl.name, realtype, quals=quals) + elif decl.__class__.__name__ == 'Pragma': + pass # skip pragma, only in pycparser 2.15 + else: + raise CDefError("unexpected <%s>: this construct is valid " + "C but not valid in cdef()" % + decl.__class__.__name__, decl) + except CDefError as e: + if len(e.args) == 1: + e.args = e.args + (current_decl,) + raise + except FFIError as e: + msg = self._convert_pycparser_error(e, csource) + if msg: + e.args = (e.args[0] + "\n *** Err: %s" % msg,) + raise + + def _add_constants(self, key, val): + if key in self._int_constants: + if self._int_constants[key] == val: + return # ignore identical double declarations + raise FFIError( + "multiple declarations of constant: %s" % (key,)) + self._int_constants[key] = val + + def _add_integer_constant(self, name, int_str): + int_str = int_str.lower().rstrip("ul") + neg = int_str.startswith('-') + if neg: + int_str = int_str[1:] + # "010" is not valid oct in py3 + if (int_str.startswith("0") and int_str != '0' + and not int_str.startswith("0x")): + int_str = "0o" + int_str[1:] + pyvalue = int(int_str, 0) + if neg: + pyvalue = -pyvalue + self._add_constants(name, pyvalue) + self._declare('macro ' + name, pyvalue) + + def _process_macros(self, macros): + for key, value in macros.items(): + value = value.strip() + if _r_int_literal.match(value): + self._add_integer_constant(key, value) + elif value == '...': + self._declare('macro ' + key, value) + else: + raise CDefError( + 'only supports one of the following syntax:\n' + ' #define %s ... (literally dot-dot-dot)\n' + ' #define %s NUMBER (with NUMBER an integer' + ' constant, decimal/hex/octal)\n' + 'got:\n' + ' #define %s %s' + % (key, key, key, value)) + + def _declare_function(self, tp, quals, decl): + tp = self._get_type_pointer(tp, quals) + if self._options.get('dllexport'): + tag = 'dllexport_python ' + elif self._inside_extern_python == '__cffi_extern_python_start': + tag = 'extern_python ' + elif self._inside_extern_python == '__cffi_extern_python_plus_c_start': + tag = 'extern_python_plus_c ' + else: + tag = 'function ' + self._declare(tag + decl.name, tp) + + def _parse_decl(self, decl): + node = decl.type + if isinstance(node, pycparser.c_ast.FuncDecl): + tp, quals = self._get_type_and_quals(node, name=decl.name) + assert isinstance(tp, model.RawFunctionType) + self._declare_function(tp, quals, decl) + else: + if isinstance(node, pycparser.c_ast.Struct): + self._get_struct_union_enum_type('struct', node) + elif isinstance(node, pycparser.c_ast.Union): + self._get_struct_union_enum_type('union', node) + elif isinstance(node, pycparser.c_ast.Enum): + self._get_struct_union_enum_type('enum', node) + elif not decl.name: + raise CDefError("construct does not declare any variable", + decl) + # + if decl.name: + tp, quals = self._get_type_and_quals(node, + partial_length_ok=True) + if tp.is_raw_function: + self._declare_function(tp, quals, decl) + elif (tp.is_integer_type() and + hasattr(decl, 'init') and + hasattr(decl.init, 'value') and + _r_int_literal.match(decl.init.value)): + self._add_integer_constant(decl.name, decl.init.value) + elif (tp.is_integer_type() and + isinstance(decl.init, pycparser.c_ast.UnaryOp) and + decl.init.op == '-' and + hasattr(decl.init.expr, 'value') and + _r_int_literal.match(decl.init.expr.value)): + self._add_integer_constant(decl.name, + '-' + decl.init.expr.value) + elif (tp is model.void_type and + decl.name.startswith('__cffi_extern_python_')): + # hack: `extern "Python"` in the C source is replaced + # with "void __cffi_extern_python_start;" and + # "void __cffi_extern_python_stop;" + self._inside_extern_python = decl.name + else: + if self._inside_extern_python !='__cffi_extern_python_stop': + raise CDefError( + "cannot declare constants or " + "variables with 'extern \"Python\"'") + if (quals & model.Q_CONST) and not tp.is_array_type: + self._declare('constant ' + decl.name, tp, quals=quals) + else: + self._declare('variable ' + decl.name, tp, quals=quals) + + def parse_type(self, cdecl): + return self.parse_type_and_quals(cdecl)[0] + + def parse_type_and_quals(self, cdecl): + ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2] + assert not macros + exprnode = ast.ext[-1].type.args.params[0] + if isinstance(exprnode, pycparser.c_ast.ID): + raise CDefError("unknown identifier '%s'" % (exprnode.name,)) + return self._get_type_and_quals(exprnode.type) + + def _declare(self, name, obj, included=False, quals=0): + if name in self._declarations: + prevobj, prevquals = self._declarations[name] + if prevobj is obj and prevquals == quals: + return + if not self._options.get('override'): + raise FFIError( + "multiple declarations of %s (for interactive usage, " + "try cdef(xx, override=True))" % (name,)) + assert '__dotdotdot__' not in name.split() + self._declarations[name] = (obj, quals) + if included: + self._included_declarations.add(obj) + + def _extract_quals(self, type): + quals = 0 + if isinstance(type, (pycparser.c_ast.TypeDecl, + pycparser.c_ast.PtrDecl)): + if 'const' in type.quals: + quals |= model.Q_CONST + if 'volatile' in type.quals: + quals |= model.Q_VOLATILE + if 'restrict' in type.quals: + quals |= model.Q_RESTRICT + return quals + + def _get_type_pointer(self, type, quals, declname=None): + if isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + if (isinstance(type, model.StructOrUnionOrEnum) and + type.name.startswith('$') and type.name[1:].isdigit() and + type.forcename is None and declname is not None): + return model.NamedPointerType(type, declname, quals) + return model.PointerType(type, quals) + + def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False): + # first, dereference typedefs, if we have it already parsed, we're good + if (isinstance(typenode, pycparser.c_ast.TypeDecl) and + isinstance(typenode.type, pycparser.c_ast.IdentifierType) and + len(typenode.type.names) == 1 and + ('typedef ' + typenode.type.names[0]) in self._declarations): + tp, quals = self._declarations['typedef ' + typenode.type.names[0]] + quals |= self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.ArrayDecl): + # array type + if typenode.dim is None: + length = None + else: + length = self._parse_constant( + typenode.dim, partial_length_ok=partial_length_ok) + tp, quals = self._get_type_and_quals(typenode.type, + partial_length_ok=partial_length_ok) + return model.ArrayType(tp, length), quals + # + if isinstance(typenode, pycparser.c_ast.PtrDecl): + # pointer type + itemtype, itemquals = self._get_type_and_quals(typenode.type) + tp = self._get_type_pointer(itemtype, itemquals, declname=name) + quals = self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.TypeDecl): + quals = self._extract_quals(typenode) + type = typenode.type + if isinstance(type, pycparser.c_ast.IdentifierType): + # assume a primitive type. get it from .names, but reduce + # synonyms to a single chosen combination + names = list(type.names) + if names != ['signed', 'char']: # keep this unmodified + prefixes = {} + while names: + name = names[0] + if name in ('short', 'long', 'signed', 'unsigned'): + prefixes[name] = prefixes.get(name, 0) + 1 + del names[0] + else: + break + # ignore the 'signed' prefix below, and reorder the others + newnames = [] + for prefix in ('unsigned', 'short', 'long'): + for i in range(prefixes.get(prefix, 0)): + newnames.append(prefix) + if not names: + names = ['int'] # implicitly + if names == ['int']: # but kill it if 'short' or 'long' + if 'short' in prefixes or 'long' in prefixes: + names = [] + names = newnames + names + ident = ' '.join(names) + if ident == 'void': + return model.void_type, quals + if ident == '__dotdotdot__': + raise FFIError(':%d: bad usage of "..."' % + typenode.coord.line) + tp0, quals0 = resolve_common_type(self, ident) + return tp0, (quals | quals0) + # + if isinstance(type, pycparser.c_ast.Struct): + # 'struct foobar' + tp = self._get_struct_union_enum_type('struct', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Union): + # 'union foobar' + tp = self._get_struct_union_enum_type('union', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Enum): + # 'enum foobar' + tp = self._get_struct_union_enum_type('enum', type, name) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.FuncDecl): + # a function type + return self._parse_function_type(typenode, name), 0 + # + # nested anonymous structs or unions end up here + if isinstance(typenode, pycparser.c_ast.Struct): + return self._get_struct_union_enum_type('struct', typenode, name, + nested=True), 0 + if isinstance(typenode, pycparser.c_ast.Union): + return self._get_struct_union_enum_type('union', typenode, name, + nested=True), 0 + # + raise FFIError(":%d: bad or unsupported type declaration" % + typenode.coord.line) + + def _parse_function_type(self, typenode, funcname=None): + params = list(getattr(typenode.args, 'params', [])) + for i, arg in enumerate(params): + if not hasattr(arg, 'type'): + raise CDefError("%s arg %d: unknown type '%s'" + " (if you meant to use the old C syntax of giving" + " untyped arguments, it is not supported)" + % (funcname or 'in expression', i + 1, + getattr(arg, 'name', '?'))) + ellipsis = ( + len(params) > 0 and + isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and + isinstance(params[-1].type.type, + pycparser.c_ast.IdentifierType) and + params[-1].type.type.names == ['__dotdotdot__']) + if ellipsis: + params.pop() + if not params: + raise CDefError( + "%s: a function with only '(...)' as argument" + " is not correct C" % (funcname or 'in expression')) + args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) + for argdeclnode in params] + if not ellipsis and args == [model.void_type]: + args = [] + result, quals = self._get_type_and_quals(typenode.type) + # the 'quals' on the result type are ignored. HACK: we absure them + # to detect __stdcall functions: we textually replace "__stdcall" + # with "volatile volatile const" above. + abi = None + if hasattr(typenode.type, 'quals'): # else, probable syntax error anyway + if typenode.type.quals[-3:] == ['volatile', 'volatile', 'const']: + abi = '__stdcall' + return model.RawFunctionType(tuple(args), result, ellipsis, abi) + + def _as_func_arg(self, type, quals): + if isinstance(type, model.ArrayType): + return model.PointerType(type.item, quals) + elif isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + else: + return type + + def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): + # First, a level of caching on the exact 'type' node of the AST. + # This is obscure, but needed because pycparser "unrolls" declarations + # such as "typedef struct { } foo_t, *foo_p" and we end up with + # an AST that is not a tree, but a DAG, with the "type" node of the + # two branches foo_t and foo_p of the trees being the same node. + # It's a bit silly but detecting "DAG-ness" in the AST tree seems + # to be the only way to distinguish this case from two independent + # structs. See test_struct_with_two_usages. + try: + return self._structnode2type[type] + except KeyError: + pass + # + # Note that this must handle parsing "struct foo" any number of + # times and always return the same StructType object. Additionally, + # one of these times (not necessarily the first), the fields of + # the struct can be specified with "struct foo { ...fields... }". + # If no name is given, then we have to create a new anonymous struct + # with no caching; in this case, the fields are either specified + # right now or never. + # + force_name = name + name = type.name + # + # get the type or create it if needed + if name is None: + # 'force_name' is used to guess a more readable name for + # anonymous structs, for the common case "typedef struct { } foo". + if force_name is not None: + explicit_name = '$%s' % force_name + else: + self._anonymous_counter += 1 + explicit_name = '$%d' % self._anonymous_counter + tp = None + else: + explicit_name = name + key = '%s %s' % (kind, name) + tp, _ = self._declarations.get(key, (None, None)) + # + if tp is None: + if kind == 'struct': + tp = model.StructType(explicit_name, None, None, None) + elif kind == 'union': + tp = model.UnionType(explicit_name, None, None, None) + elif kind == 'enum': + if explicit_name == '__dotdotdot__': + raise CDefError("Enums cannot be declared with ...") + tp = self._build_enum_type(explicit_name, type.values) + else: + raise AssertionError("kind = %r" % (kind,)) + if name is not None: + self._declare(key, tp) + else: + if kind == 'enum' and type.values is not None: + raise NotImplementedError( + "enum %s: the '{}' declaration should appear on the first " + "time the enum is mentioned, not later" % explicit_name) + if not tp.forcename: + tp.force_the_name(force_name) + if tp.forcename and '$' in tp.name: + self._declare('anonymous %s' % tp.forcename, tp) + # + self._structnode2type[type] = tp + # + # enums: done here + if kind == 'enum': + return tp + # + # is there a 'type.decls'? If yes, then this is the place in the + # C sources that declare the fields. If no, then just return the + # existing type, possibly still incomplete. + if type.decls is None: + return tp + # + if tp.fldnames is not None: + raise CDefError("duplicate declaration of struct %s" % name) + fldnames = [] + fldtypes = [] + fldbitsize = [] + fldquals = [] + for decl in type.decls: + if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and + ''.join(decl.type.names) == '__dotdotdot__'): + # XXX pycparser is inconsistent: 'names' should be a list + # of strings, but is sometimes just one string. Use + # str.join() as a way to cope with both. + self._make_partial(tp, nested) + continue + if decl.bitsize is None: + bitsize = -1 + else: + bitsize = self._parse_constant(decl.bitsize) + self._partial_length = False + type, fqual = self._get_type_and_quals(decl.type, + partial_length_ok=True) + if self._partial_length: + self._make_partial(tp, nested) + if isinstance(type, model.StructType) and type.partial: + self._make_partial(tp, nested) + fldnames.append(decl.name or '') + fldtypes.append(type) + fldbitsize.append(bitsize) + fldquals.append(fqual) + tp.fldnames = tuple(fldnames) + tp.fldtypes = tuple(fldtypes) + tp.fldbitsize = tuple(fldbitsize) + tp.fldquals = tuple(fldquals) + if fldbitsize != [-1] * len(fldbitsize): + if isinstance(tp, model.StructType) and tp.partial: + raise NotImplementedError("%s: using both bitfields and '...;'" + % (tp,)) + tp.packed = self._options.get('packed') + if tp.completed: # must be re-completed: it is not opaque any more + tp.completed = 0 + self._recomplete.append(tp) + return tp + + def _make_partial(self, tp, nested): + if not isinstance(tp, model.StructOrUnion): + raise CDefError("%s cannot be partial" % (tp,)) + if not tp.has_c_name() and not nested: + raise NotImplementedError("%s is partial but has no C name" %(tp,)) + tp.partial = True + + def _parse_constant(self, exprnode, partial_length_ok=False): + # for now, limited to expressions that are an immediate number + # or positive/negative number + if isinstance(exprnode, pycparser.c_ast.Constant): + s = exprnode.value + if s.startswith('0'): + if s.startswith('0x') or s.startswith('0X'): + return int(s, 16) + return int(s, 8) + elif '1' <= s[0] <= '9': + return int(s, 10) + elif s[0] == "'" and s[-1] == "'" and ( + len(s) == 3 or (len(s) == 4 and s[1] == "\\")): + return ord(s[-2]) + else: + raise CDefError("invalid constant %r" % (s,)) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '+'): + return self._parse_constant(exprnode.expr) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '-'): + return -self._parse_constant(exprnode.expr) + # load previously defined int constant + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name in self._int_constants): + return self._int_constants[exprnode.name] + # + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name == '__dotdotdotarray__'): + if partial_length_ok: + self._partial_length = True + return '...' + raise FFIError(":%d: unsupported '[...]' here, cannot derive " + "the actual array length in this context" + % exprnode.coord.line) + # + if (isinstance(exprnode, pycparser.c_ast.BinaryOp) and + exprnode.op == '+'): + return (self._parse_constant(exprnode.left) + + self._parse_constant(exprnode.right)) + # + if (isinstance(exprnode, pycparser.c_ast.BinaryOp) and + exprnode.op == '-'): + return (self._parse_constant(exprnode.left) - + self._parse_constant(exprnode.right)) + # + raise FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) + + def _build_enum_type(self, explicit_name, decls): + if decls is not None: + partial = False + enumerators = [] + enumvalues = [] + nextenumvalue = 0 + for enum in decls.enumerators: + if _r_enum_dotdotdot.match(enum.name): + partial = True + continue + if enum.value is not None: + nextenumvalue = self._parse_constant(enum.value) + enumerators.append(enum.name) + enumvalues.append(nextenumvalue) + self._add_constants(enum.name, nextenumvalue) + nextenumvalue += 1 + enumerators = tuple(enumerators) + enumvalues = tuple(enumvalues) + tp = model.EnumType(explicit_name, enumerators, enumvalues) + tp.partial = partial + else: # opaque enum + tp = model.EnumType(explicit_name, (), ()) + return tp + + def include(self, other): + for name, (tp, quals) in other._declarations.items(): + if name.startswith('anonymous $enum_$'): + continue # fix for test_anonymous_enum_include + kind = name.split(' ', 1)[0] + if kind in ('struct', 'union', 'enum', 'anonymous', 'typedef'): + self._declare(name, tp, included=True, quals=quals) + for k, v in other._int_constants.items(): + self._add_constants(k, v) + + def _get_unknown_type(self, decl): + typenames = decl.type.type.names + if typenames == ['__dotdotdot__']: + return model.unknown_type(decl.name) + + if typenames == ['__dotdotdotint__']: + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef int... %s'" % decl.name + return model.UnknownIntegerType(decl.name) + + if typenames == ['__dotdotdotfloat__']: + # note: not for 'long double' so far + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef float... %s'" % decl.name + return model.UnknownFloatType(decl.name) + + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) + + def _get_unknown_ptr_type(self, decl): + if decl.type.type.type.names == ['__dotdotdot__']: + return model.unknown_ptr_type(decl.name) + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) diff --git a/cffi/error.py b/cffi/error.py new file mode 100644 index 0000000..0a27247 --- /dev/null +++ b/cffi/error.py @@ -0,0 +1,31 @@ + +class FFIError(Exception): + __module__ = 'cffi' + +class CDefError(Exception): + __module__ = 'cffi' + def __str__(self): + try: + current_decl = self.args[1] + filename = current_decl.coord.file + linenum = current_decl.coord.line + prefix = '%s:%d: ' % (filename, linenum) + except (AttributeError, TypeError, IndexError): + prefix = '' + return '%s%s' % (prefix, self.args[0]) + +class VerificationError(Exception): + """ An error raised when verification fails + """ + __module__ = 'cffi' + +class VerificationMissing(Exception): + """ An error raised when incomplete structures are passed into + cdef, but no verification has been done + """ + __module__ = 'cffi' + +class PkgConfigError(Exception): + """ An error raised for missing modules in pkg-config + """ + __module__ = 'cffi' diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py new file mode 100644 index 0000000..8531346 --- /dev/null +++ b/cffi/ffiplatform.py @@ -0,0 +1,127 @@ +import sys, os +from .error import VerificationError + + +LIST_OF_FILE_NAMES = ['sources', 'include_dirs', 'library_dirs', + 'extra_objects', 'depends'] + +def get_extension(srcfilename, modname, sources=(), **kwds): + _hack_at_distutils() + from distutils.core import Extension + allsources = [srcfilename] + for src in sources: + allsources.append(os.path.normpath(src)) + return Extension(name=modname, sources=allsources, **kwds) + +def compile(tmpdir, ext, compiler_verbose=0, debug=None): + """Compile a C extension module using distutils.""" + + _hack_at_distutils() + saved_environ = os.environ.copy() + try: + outputfilename = _build(tmpdir, ext, compiler_verbose, debug) + outputfilename = os.path.abspath(outputfilename) + finally: + # workaround for a distutils bugs where some env vars can + # become longer and longer every time it is used + for key, value in saved_environ.items(): + if os.environ.get(key) != value: + os.environ[key] = value + return outputfilename + +def _build(tmpdir, ext, compiler_verbose=0, debug=None): + # XXX compact but horrible :-( + from distutils.core import Distribution + import distutils.errors, distutils.log + # + dist = Distribution({'ext_modules': [ext]}) + dist.parse_config_files() + options = dist.get_option_dict('build_ext') + if debug is None: + debug = sys.flags.debug + options['debug'] = ('ffiplatform', debug) + options['force'] = ('ffiplatform', True) + options['build_lib'] = ('ffiplatform', tmpdir) + options['build_temp'] = ('ffiplatform', tmpdir) + # + try: + old_level = distutils.log.set_threshold(0) or 0 + try: + distutils.log.set_verbosity(compiler_verbose) + dist.run_command('build_ext') + cmd_obj = dist.get_command_obj('build_ext') + [soname] = cmd_obj.get_outputs() + finally: + distutils.log.set_threshold(old_level) + except (distutils.errors.CompileError, + distutils.errors.LinkError) as e: + raise VerificationError('%s: %s' % (e.__class__.__name__, e)) + # + return soname + +try: + from os.path import samefile +except ImportError: + def samefile(f1, f2): + return os.path.abspath(f1) == os.path.abspath(f2) + +def maybe_relative_path(path): + if not os.path.isabs(path): + return path # already relative + dir = path + names = [] + while True: + prevdir = dir + dir, name = os.path.split(prevdir) + if dir == prevdir or not dir: + return path # failed to make it relative + names.append(name) + try: + if samefile(dir, os.curdir): + names.reverse() + return os.path.join(*names) + except OSError: + pass + +# ____________________________________________________________ + +try: + int_or_long = (int, long) + import cStringIO +except NameError: + int_or_long = int # Python 3 + import io as cStringIO + +def _flatten(x, f): + if isinstance(x, str): + f.write('%ds%s' % (len(x), x)) + elif isinstance(x, dict): + keys = sorted(x.keys()) + f.write('%dd' % len(keys)) + for key in keys: + _flatten(key, f) + _flatten(x[key], f) + elif isinstance(x, (list, tuple)): + f.write('%dl' % len(x)) + for value in x: + _flatten(value, f) + elif isinstance(x, int_or_long): + f.write('%di' % (x,)) + else: + raise TypeError( + "the keywords to verify() contains unsupported object %r" % (x,)) + +def flatten(x): + f = cStringIO.StringIO() + _flatten(x, f) + return f.getvalue() + +def _hack_at_distutils(): + # Windows-only workaround for some configurations: see + # https://bugs.python.org/issue23246 (Python 2.7 with + # a specific MS compiler suite download) + if sys.platform == "win32": + try: + import setuptools # for side-effects, patches distutils + except ImportError: + pass diff --git a/cffi/lock.py b/cffi/lock.py new file mode 100644 index 0000000..db91b71 --- /dev/null +++ b/cffi/lock.py @@ -0,0 +1,30 @@ +import sys + +if sys.version_info < (3,): + try: + from thread import allocate_lock + except ImportError: + from dummy_thread import allocate_lock +else: + try: + from _thread import allocate_lock + except ImportError: + from _dummy_thread import allocate_lock + + +##import sys +##l1 = allocate_lock + +##class allocate_lock(object): +## def __init__(self): +## self._real = l1() +## def __enter__(self): +## for i in range(4, 0, -1): +## print sys._getframe(i).f_code +## print +## return self._real.__enter__() +## def __exit__(self, *args): +## return self._real.__exit__(*args) +## def acquire(self, f): +## assert f is False +## return self._real.acquire(f) diff --git a/cffi/model.py b/cffi/model.py new file mode 100644 index 0000000..5f1b0d2 --- /dev/null +++ b/cffi/model.py @@ -0,0 +1,614 @@ +import types +import weakref + +from .lock import allocate_lock +from .error import CDefError, VerificationError, VerificationMissing + +# type qualifiers +Q_CONST = 0x01 +Q_RESTRICT = 0x02 +Q_VOLATILE = 0x04 + +def qualify(quals, replace_with): + if quals & Q_CONST: + replace_with = ' const ' + replace_with.lstrip() + if quals & Q_VOLATILE: + replace_with = ' volatile ' + replace_with.lstrip() + if quals & Q_RESTRICT: + # It seems that __restrict is supported by gcc and msvc. + # If you hit some different compiler, add a #define in + # _cffi_include.h for it (and in its copies, documented there) + replace_with = ' __restrict ' + replace_with.lstrip() + return replace_with + + +class BaseTypeByIdentity(object): + is_array_type = False + is_raw_function = False + + def get_c_name(self, replace_with='', context='a C file', quals=0): + result = self.c_name_with_marker + assert result.count('&') == 1 + # some logic duplication with ffi.getctype()... :-( + replace_with = replace_with.strip() + if replace_with: + if replace_with.startswith('*') and '&[' in result: + replace_with = '(%s)' % replace_with + elif not replace_with[0] in '[(': + replace_with = ' ' + replace_with + replace_with = qualify(quals, replace_with) + result = result.replace('&', replace_with) + if '$' in result: + raise VerificationError( + "cannot generate '%s' in %s: unknown type name" + % (self._get_c_name(), context)) + return result + + def _get_c_name(self): + return self.c_name_with_marker.replace('&', '') + + def has_c_name(self): + return '$' not in self._get_c_name() + + def is_integer_type(self): + return False + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + try: + BType = ffi._cached_btypes[self] + except KeyError: + BType = self.build_backend_type(ffi, finishlist) + BType2 = ffi._cached_btypes.setdefault(self, BType) + assert BType2 is BType + return BType + + def __repr__(self): + return '<%s>' % (self._get_c_name(),) + + def _get_items(self): + return [(name, getattr(self, name)) for name in self._attrs_] + + +class BaseType(BaseTypeByIdentity): + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self._get_items() == other._get_items()) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.__class__, tuple(self._get_items()))) + + +class VoidType(BaseType): + _attrs_ = () + + def __init__(self): + self.c_name_with_marker = 'void&' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_void_type') + +void_type = VoidType() + + +class BasePrimitiveType(BaseType): + def is_complex_type(self): + return False + + +class PrimitiveType(BasePrimitiveType): + _attrs_ = ('name',) + + ALL_PRIMITIVE_TYPES = { + 'char': 'c', + 'short': 'i', + 'int': 'i', + 'long': 'i', + 'long long': 'i', + 'signed char': 'i', + 'unsigned char': 'i', + 'unsigned short': 'i', + 'unsigned int': 'i', + 'unsigned long': 'i', + 'unsigned long long': 'i', + 'float': 'f', + 'double': 'f', + 'long double': 'f', + 'float _Complex': 'j', + 'double _Complex': 'j', + '_Bool': 'i', + # the following types are not primitive in the C sense + 'wchar_t': 'c', + 'char16_t': 'c', + 'char32_t': 'c', + 'int8_t': 'i', + 'uint8_t': 'i', + 'int16_t': 'i', + 'uint16_t': 'i', + 'int32_t': 'i', + 'uint32_t': 'i', + 'int64_t': 'i', + 'uint64_t': 'i', + 'int_least8_t': 'i', + 'uint_least8_t': 'i', + 'int_least16_t': 'i', + 'uint_least16_t': 'i', + 'int_least32_t': 'i', + 'uint_least32_t': 'i', + 'int_least64_t': 'i', + 'uint_least64_t': 'i', + 'int_fast8_t': 'i', + 'uint_fast8_t': 'i', + 'int_fast16_t': 'i', + 'uint_fast16_t': 'i', + 'int_fast32_t': 'i', + 'uint_fast32_t': 'i', + 'int_fast64_t': 'i', + 'uint_fast64_t': 'i', + 'intptr_t': 'i', + 'uintptr_t': 'i', + 'intmax_t': 'i', + 'uintmax_t': 'i', + 'ptrdiff_t': 'i', + 'size_t': 'i', + 'ssize_t': 'i', + } + + def __init__(self, name): + assert name in self.ALL_PRIMITIVE_TYPES + self.name = name + self.c_name_with_marker = name + '&' + + def is_char_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'c' + def is_integer_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'i' + def is_float_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'f' + def is_complex_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'j' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_primitive_type', self.name) + + +class UnknownIntegerType(BasePrimitiveType): + _attrs_ = ('name',) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def is_integer_type(self): + return True + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("integer type '%s' can only be used after " + "compilation" % self.name) + +class UnknownFloatType(BasePrimitiveType): + _attrs_ = ('name', ) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("float type '%s' can only be used after " + "compilation" % self.name) + + +class BaseFunctionType(BaseType): + _attrs_ = ('args', 'result', 'ellipsis', 'abi') + + def __init__(self, args, result, ellipsis, abi=None): + self.args = args + self.result = result + self.ellipsis = ellipsis + self.abi = abi + # + reprargs = [arg._get_c_name() for arg in self.args] + if self.ellipsis: + reprargs.append('...') + reprargs = reprargs or ['void'] + replace_with = self._base_pattern % (', '.join(reprargs),) + if abi is not None: + replace_with = replace_with[:1] + abi + ' ' + replace_with[1:] + self.c_name_with_marker = ( + self.result.c_name_with_marker.replace('&', replace_with)) + + +class RawFunctionType(BaseFunctionType): + # Corresponds to a C type like 'int(int)', which is the C type of + # a function, but not a pointer-to-function. The backend has no + # notion of such a type; it's used temporarily by parsing. + _base_pattern = '(&)(%s)' + is_raw_function = True + + def build_backend_type(self, ffi, finishlist): + raise CDefError("cannot render the type %r: it is a function " + "type, not a pointer-to-function type" % (self,)) + + def as_function_pointer(self): + return FunctionPtrType(self.args, self.result, self.ellipsis, self.abi) + + +class FunctionPtrType(BaseFunctionType): + _base_pattern = '(*&)(%s)' + + def build_backend_type(self, ffi, finishlist): + result = self.result.get_cached_btype(ffi, finishlist) + args = [] + for tp in self.args: + args.append(tp.get_cached_btype(ffi, finishlist)) + abi_args = () + if self.abi == "__stdcall": + if not self.ellipsis: # __stdcall ignored for variadic funcs + try: + abi_args = (ffi._backend.FFI_STDCALL,) + except AttributeError: + pass + return global_cache(self, ffi, 'new_function_type', + tuple(args), result, self.ellipsis, *abi_args) + + def as_raw_function(self): + return RawFunctionType(self.args, self.result, self.ellipsis, self.abi) + + +class PointerType(BaseType): + _attrs_ = ('totype', 'quals') + + def __init__(self, totype, quals=0): + self.totype = totype + self.quals = quals + extra = qualify(quals, " *&") + if totype.is_array_type: + extra = "(%s)" % (extra.lstrip(),) + self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra) + + def build_backend_type(self, ffi, finishlist): + BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True) + return global_cache(self, ffi, 'new_pointer_type', BItem) + +voidp_type = PointerType(void_type) + +def ConstPointerType(totype): + return PointerType(totype, Q_CONST) + +const_voidp_type = ConstPointerType(void_type) + + +class NamedPointerType(PointerType): + _attrs_ = ('totype', 'name') + + def __init__(self, totype, name, quals=0): + PointerType.__init__(self, totype, quals) + self.name = name + self.c_name_with_marker = name + '&' + + +class ArrayType(BaseType): + _attrs_ = ('item', 'length') + is_array_type = True + + def __init__(self, item, length): + self.item = item + self.length = length + # + if length is None: + brackets = '&[]' + elif length == '...': + brackets = '&[/*...*/]' + else: + brackets = '&[%s]' % length + self.c_name_with_marker = ( + self.item.c_name_with_marker.replace('&', brackets)) + + def resolve_length(self, newlength): + return ArrayType(self.item, newlength) + + def build_backend_type(self, ffi, finishlist): + if self.length == '...': + raise CDefError("cannot render the type %r: unknown length" % + (self,)) + self.item.get_cached_btype(ffi, finishlist) # force the item BType + BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist) + return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length) + +char_array_type = ArrayType(PrimitiveType('char'), None) + + +class StructOrUnionOrEnum(BaseTypeByIdentity): + _attrs_ = ('name',) + forcename = None + + def build_c_name_with_marker(self): + name = self.forcename or '%s %s' % (self.kind, self.name) + self.c_name_with_marker = name + '&' + + def force_the_name(self, forcename): + self.forcename = forcename + self.build_c_name_with_marker() + + def get_official_name(self): + assert self.c_name_with_marker.endswith('&') + return self.c_name_with_marker[:-1] + + +class StructOrUnion(StructOrUnionOrEnum): + fixedlayout = None + completed = 0 + partial = False + packed = 0 + + def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None): + self.name = name + self.fldnames = fldnames + self.fldtypes = fldtypes + self.fldbitsize = fldbitsize + self.fldquals = fldquals + self.build_c_name_with_marker() + + def anonymous_struct_fields(self): + if self.fldtypes is not None: + for name, type in zip(self.fldnames, self.fldtypes): + if name == '' and isinstance(type, StructOrUnion): + yield type + + def enumfields(self, expand_anonymous_struct_union=True): + fldquals = self.fldquals + if fldquals is None: + fldquals = (0,) * len(self.fldnames) + for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes, + self.fldbitsize, fldquals): + if (name == '' and isinstance(type, StructOrUnion) + and expand_anonymous_struct_union): + # nested anonymous struct/union + for result in type.enumfields(): + yield result + else: + yield (name, type, bitsize, quals) + + def force_flatten(self): + # force the struct or union to have a declaration that lists + # directly all fields returned by enumfields(), flattening + # nested anonymous structs/unions. + names = [] + types = [] + bitsizes = [] + fldquals = [] + for name, type, bitsize, quals in self.enumfields(): + names.append(name) + types.append(type) + bitsizes.append(bitsize) + fldquals.append(quals) + self.fldnames = tuple(names) + self.fldtypes = tuple(types) + self.fldbitsize = tuple(bitsizes) + self.fldquals = tuple(fldquals) + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + BType = StructOrUnionOrEnum.get_cached_btype(self, ffi, finishlist, + can_delay) + if not can_delay: + self.finish_backend_type(ffi, finishlist) + return BType + + def finish_backend_type(self, ffi, finishlist): + if self.completed: + if self.completed != 2: + raise NotImplementedError("recursive structure declaration " + "for '%s'" % (self.name,)) + return + BType = ffi._cached_btypes[self] + # + self.completed = 1 + # + if self.fldtypes is None: + pass # not completing it: it's an opaque struct + # + elif self.fixedlayout is None: + fldtypes = [tp.get_cached_btype(ffi, finishlist) + for tp in self.fldtypes] + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize)) + extra_flags = () + if self.packed: + if self.packed == 1: + extra_flags = (8,) # SF_PACKED + else: + extra_flags = (0, self.packed) + ffi._backend.complete_struct_or_union(BType, lst, self, + -1, -1, *extra_flags) + # + else: + fldtypes = [] + fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout + for i in range(len(self.fldnames)): + fsize = fieldsize[i] + ftype = self.fldtypes[i] + # + if isinstance(ftype, ArrayType) and ftype.length == '...': + # fix the length to match the total size + BItemType = ftype.item.get_cached_btype(ffi, finishlist) + nlen, nrest = divmod(fsize, ffi.sizeof(BItemType)) + if nrest != 0: + self._verification_error( + "field '%s.%s' has a bogus size?" % ( + self.name, self.fldnames[i] or '{}')) + ftype = ftype.resolve_length(nlen) + self.fldtypes = (self.fldtypes[:i] + (ftype,) + + self.fldtypes[i+1:]) + # + BFieldType = ftype.get_cached_btype(ffi, finishlist) + if isinstance(ftype, ArrayType) and ftype.length is None: + assert fsize == 0 + else: + bitemsize = ffi.sizeof(BFieldType) + if bitemsize != fsize: + self._verification_error( + "field '%s.%s' is declared as %d bytes, but is " + "really %d bytes" % (self.name, + self.fldnames[i] or '{}', + bitemsize, fsize)) + fldtypes.append(BFieldType) + # + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs)) + ffi._backend.complete_struct_or_union(BType, lst, self, + totalsize, totalalignment) + self.completed = 2 + + def _verification_error(self, msg): + raise VerificationError(msg) + + def check_not_partial(self): + if self.partial and self.fixedlayout is None: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + finishlist.append(self) + # + return global_cache(self, ffi, 'new_%s_type' % self.kind, + self.get_official_name(), key=self) + + +class StructType(StructOrUnion): + kind = 'struct' + + +class UnionType(StructOrUnion): + kind = 'union' + + +class EnumType(StructOrUnionOrEnum): + kind = 'enum' + partial = False + partial_resolved = False + + def __init__(self, name, enumerators, enumvalues, baseinttype=None): + self.name = name + self.enumerators = enumerators + self.enumvalues = enumvalues + self.baseinttype = baseinttype + self.build_c_name_with_marker() + + def force_the_name(self, forcename): + StructOrUnionOrEnum.force_the_name(self, forcename) + if self.forcename is None: + name = self.get_official_name() + self.forcename = '$' + name.replace(' ', '_') + + def check_not_partial(self): + if self.partial and not self.partial_resolved: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + base_btype = self.build_baseinttype(ffi, finishlist) + return global_cache(self, ffi, 'new_enum_type', + self.get_official_name(), + self.enumerators, self.enumvalues, + base_btype, key=self) + + def build_baseinttype(self, ffi, finishlist): + if self.baseinttype is not None: + return self.baseinttype.get_cached_btype(ffi, finishlist) + # + if self.enumvalues: + smallest_value = min(self.enumvalues) + largest_value = max(self.enumvalues) + else: + import warnings + try: + # XXX! The goal is to ensure that the warnings.warn() + # will not suppress the warning. We want to get it + # several times if we reach this point several times. + __warningregistry__.clear() + except NameError: + pass + warnings.warn("%r has no values explicitly defined; " + "guessing that it is equivalent to 'unsigned int'" + % self._get_c_name()) + smallest_value = largest_value = 0 + if smallest_value < 0: # needs a signed type + sign = 1 + candidate1 = PrimitiveType("int") + candidate2 = PrimitiveType("long") + else: + sign = 0 + candidate1 = PrimitiveType("unsigned int") + candidate2 = PrimitiveType("unsigned long") + btype1 = candidate1.get_cached_btype(ffi, finishlist) + btype2 = candidate2.get_cached_btype(ffi, finishlist) + size1 = ffi.sizeof(btype1) + size2 = ffi.sizeof(btype2) + if (smallest_value >= ((-1) << (8*size1-1)) and + largest_value < (1 << (8*size1-sign))): + return btype1 + if (smallest_value >= ((-1) << (8*size2-1)) and + largest_value < (1 << (8*size2-sign))): + return btype2 + raise CDefError("%s values don't all fit into either 'long' " + "or 'unsigned long'" % self._get_c_name()) + +def unknown_type(name, structname=None): + if structname is None: + structname = '$%s' % name + tp = StructType(structname, None, None, None) + tp.force_the_name(name) + tp.origin = "unknown_type" + return tp + +def unknown_ptr_type(name, structname=None): + if structname is None: + structname = '$$%s' % name + tp = StructType(structname, None, None, None) + return NamedPointerType(tp, name) + + +global_lock = allocate_lock() +_typecache_cffi_backend = weakref.WeakValueDictionary() + +def get_typecache(backend): + # returns _typecache_cffi_backend if backend is the _cffi_backend + # module, or type(backend).__typecache if backend is an instance of + # CTypesBackend (or some FakeBackend class during tests) + if isinstance(backend, types.ModuleType): + return _typecache_cffi_backend + with global_lock: + if not hasattr(type(backend), '__typecache'): + type(backend).__typecache = weakref.WeakValueDictionary() + return type(backend).__typecache + +def global_cache(srctype, ffi, funcname, *args, **kwds): + key = kwds.pop('key', (funcname, args)) + assert not kwds + try: + return ffi._typecache[key] + except KeyError: + pass + try: + res = getattr(ffi._backend, funcname)(*args) + except NotImplementedError as e: + raise NotImplementedError("%s: %r: %s" % (funcname, srctype, e)) + # note that setdefault() on WeakValueDictionary is not atomic + # and contains a rare bug (http://bugs.python.org/issue19542); + # we have to use a lock and do it ourselves + cache = ffi._typecache + with global_lock: + res1 = cache.get(key) + if res1 is None: + cache[key] = res + return res + else: + return res1 + +def pointer_cache(ffi, BType): + return global_cache('?', ffi, 'new_pointer_type', BType) + +def attach_exception_info(e, name): + if e.args and type(e.args[0]) is str: + e.args = ('%s: %s' % (name, e.args[0]),) + e.args[1:] diff --git a/cffi/parse_c_type.h b/cffi/parse_c_type.h new file mode 100644 index 0000000..84e4ef8 --- /dev/null +++ b/cffi/parse_c_type.h @@ -0,0 +1,181 @@ + +/* This part is from file 'cffi/parse_c_type.h'. It is copied at the + beginning of C sources generated by CFFI's ffi.set_source(). */ + +typedef void *_cffi_opcode_t; + +#define _CFFI_OP(opcode, arg) (_cffi_opcode_t)(opcode | (((uintptr_t)(arg)) << 8)) +#define _CFFI_GETOP(cffi_opcode) ((unsigned char)(uintptr_t)cffi_opcode) +#define _CFFI_GETARG(cffi_opcode) (((intptr_t)cffi_opcode) >> 8) + +#define _CFFI_OP_PRIMITIVE 1 +#define _CFFI_OP_POINTER 3 +#define _CFFI_OP_ARRAY 5 +#define _CFFI_OP_OPEN_ARRAY 7 +#define _CFFI_OP_STRUCT_UNION 9 +#define _CFFI_OP_ENUM 11 +#define _CFFI_OP_FUNCTION 13 +#define _CFFI_OP_FUNCTION_END 15 +#define _CFFI_OP_NOOP 17 +#define _CFFI_OP_BITFIELD 19 +#define _CFFI_OP_TYPENAME 21 +#define _CFFI_OP_CPYTHON_BLTN_V 23 // varargs +#define _CFFI_OP_CPYTHON_BLTN_N 25 // noargs +#define _CFFI_OP_CPYTHON_BLTN_O 27 // O (i.e. a single arg) +#define _CFFI_OP_CONSTANT 29 +#define _CFFI_OP_CONSTANT_INT 31 +#define _CFFI_OP_GLOBAL_VAR 33 +#define _CFFI_OP_DLOPEN_FUNC 35 +#define _CFFI_OP_DLOPEN_CONST 37 +#define _CFFI_OP_GLOBAL_VAR_F 39 +#define _CFFI_OP_EXTERN_PYTHON 41 + +#define _CFFI_PRIM_VOID 0 +#define _CFFI_PRIM_BOOL 1 +#define _CFFI_PRIM_CHAR 2 +#define _CFFI_PRIM_SCHAR 3 +#define _CFFI_PRIM_UCHAR 4 +#define _CFFI_PRIM_SHORT 5 +#define _CFFI_PRIM_USHORT 6 +#define _CFFI_PRIM_INT 7 +#define _CFFI_PRIM_UINT 8 +#define _CFFI_PRIM_LONG 9 +#define _CFFI_PRIM_ULONG 10 +#define _CFFI_PRIM_LONGLONG 11 +#define _CFFI_PRIM_ULONGLONG 12 +#define _CFFI_PRIM_FLOAT 13 +#define _CFFI_PRIM_DOUBLE 14 +#define _CFFI_PRIM_LONGDOUBLE 15 + +#define _CFFI_PRIM_WCHAR 16 +#define _CFFI_PRIM_INT8 17 +#define _CFFI_PRIM_UINT8 18 +#define _CFFI_PRIM_INT16 19 +#define _CFFI_PRIM_UINT16 20 +#define _CFFI_PRIM_INT32 21 +#define _CFFI_PRIM_UINT32 22 +#define _CFFI_PRIM_INT64 23 +#define _CFFI_PRIM_UINT64 24 +#define _CFFI_PRIM_INTPTR 25 +#define _CFFI_PRIM_UINTPTR 26 +#define _CFFI_PRIM_PTRDIFF 27 +#define _CFFI_PRIM_SIZE 28 +#define _CFFI_PRIM_SSIZE 29 +#define _CFFI_PRIM_INT_LEAST8 30 +#define _CFFI_PRIM_UINT_LEAST8 31 +#define _CFFI_PRIM_INT_LEAST16 32 +#define _CFFI_PRIM_UINT_LEAST16 33 +#define _CFFI_PRIM_INT_LEAST32 34 +#define _CFFI_PRIM_UINT_LEAST32 35 +#define _CFFI_PRIM_INT_LEAST64 36 +#define _CFFI_PRIM_UINT_LEAST64 37 +#define _CFFI_PRIM_INT_FAST8 38 +#define _CFFI_PRIM_UINT_FAST8 39 +#define _CFFI_PRIM_INT_FAST16 40 +#define _CFFI_PRIM_UINT_FAST16 41 +#define _CFFI_PRIM_INT_FAST32 42 +#define _CFFI_PRIM_UINT_FAST32 43 +#define _CFFI_PRIM_INT_FAST64 44 +#define _CFFI_PRIM_UINT_FAST64 45 +#define _CFFI_PRIM_INTMAX 46 +#define _CFFI_PRIM_UINTMAX 47 +#define _CFFI_PRIM_FLOATCOMPLEX 48 +#define _CFFI_PRIM_DOUBLECOMPLEX 49 +#define _CFFI_PRIM_CHAR16 50 +#define _CFFI_PRIM_CHAR32 51 + +#define _CFFI__NUM_PRIM 52 +#define _CFFI__UNKNOWN_PRIM (-1) +#define _CFFI__UNKNOWN_FLOAT_PRIM (-2) +#define _CFFI__UNKNOWN_LONG_DOUBLE (-3) + +#define _CFFI__IO_FILE_STRUCT (-1) + + +struct _cffi_global_s { + const char *name; + void *address; + _cffi_opcode_t type_op; + void *size_or_direct_fn; // OP_GLOBAL_VAR: size, or 0 if unknown + // OP_CPYTHON_BLTN_*: addr of direct function +}; + +struct _cffi_getconst_s { + unsigned long long value; + const struct _cffi_type_context_s *ctx; + int gindex; +}; + +struct _cffi_struct_union_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_STRUCT_UNION + int flags; // _CFFI_F_* flags below + size_t size; + int alignment; + int first_field_index; // -> _cffi_fields array + int num_fields; +}; +#define _CFFI_F_UNION 0x01 // is a union, not a struct +#define _CFFI_F_CHECK_FIELDS 0x02 // complain if fields are not in the + // "standard layout" or if some are missing +#define _CFFI_F_PACKED 0x04 // for CHECK_FIELDS, assume a packed struct +#define _CFFI_F_EXTERNAL 0x08 // in some other ffi.include() +#define _CFFI_F_OPAQUE 0x10 // opaque + +struct _cffi_field_s { + const char *name; + size_t field_offset; + size_t field_size; + _cffi_opcode_t field_type_op; +}; + +struct _cffi_enum_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_ENUM + int type_prim; // _CFFI_PRIM_xxx + const char *enumerators; // comma-delimited string +}; + +struct _cffi_typename_s { + const char *name; + int type_index; /* if opaque, points to a possibly artificial + OP_STRUCT which is itself opaque */ +}; + +struct _cffi_type_context_s { + _cffi_opcode_t *types; + const struct _cffi_global_s *globals; + const struct _cffi_field_s *fields; + const struct _cffi_struct_union_s *struct_unions; + const struct _cffi_enum_s *enums; + const struct _cffi_typename_s *typenames; + int num_globals; + int num_struct_unions; + int num_enums; + int num_typenames; + const char *const *includes; + int num_types; + int flags; /* future extension */ +}; + +struct _cffi_parse_info_s { + const struct _cffi_type_context_s *ctx; + _cffi_opcode_t *output; + unsigned int output_size; + size_t error_location; + const char *error_message; +}; + +struct _cffi_externpy_s { + const char *name; + size_t size_of_result; + void *reserved1, *reserved2; +}; + +#ifdef _CFFI_INTERNAL +static int parse_c_type(struct _cffi_parse_info_s *info, const char *input); +static int search_in_globals(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +static int search_in_struct_unions(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +#endif diff --git a/cffi/pkgconfig.py b/cffi/pkgconfig.py new file mode 100644 index 0000000..5c93f15 --- /dev/null +++ b/cffi/pkgconfig.py @@ -0,0 +1,121 @@ +# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi +import sys, os, subprocess + +from .error import PkgConfigError + + +def merge_flags(cfg1, cfg2): + """Merge values from cffi config flags cfg2 to cf1 + + Example: + merge_flags({"libraries": ["one"]}, {"libraries": ["two"]}) + {"libraries": ["one", "two"]} + """ + for key, value in cfg2.items(): + if key not in cfg1: + cfg1[key] = value + else: + if not isinstance(cfg1[key], list): + raise TypeError("cfg1[%r] should be a list of strings" % (key,)) + if not isinstance(value, list): + raise TypeError("cfg2[%r] should be a list of strings" % (key,)) + cfg1[key].extend(value) + return cfg1 + + +def call(libname, flag, encoding=sys.getfilesystemencoding()): + """Calls pkg-config and returns the output if found + """ + a = ["pkg-config", "--print-errors"] + a.append(flag) + a.append(libname) + try: + pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except EnvironmentError as e: + raise PkgConfigError("cannot run pkg-config: %s" % (str(e).strip(),)) + + bout, berr = pc.communicate() + if pc.returncode != 0: + try: + berr = berr.decode(encoding) + except Exception: + pass + raise PkgConfigError(berr.strip()) + + if sys.version_info >= (3,) and not isinstance(bout, str): # Python 3.x + try: + bout = bout.decode(encoding) + except UnicodeDecodeError: + raise PkgConfigError("pkg-config %s %s returned bytes that cannot " + "be decoded with encoding %r:\n%r" % + (flag, libname, encoding, bout)) + + if os.altsep != '\\' and '\\' in bout: + raise PkgConfigError("pkg-config %s %s returned an unsupported " + "backslash-escaped output:\n%r" % + (flag, libname, bout)) + return bout + + +def flags_from_pkgconfig(libs): + r"""Return compiler line flags for FFI.set_source based on pkg-config output + + Usage + ... + ffibuilder.set_source("_foo", pkgconfig = ["libfoo", "libbar >= 1.8.3"]) + + If pkg-config is installed on build machine, then arguments include_dirs, + library_dirs, libraries, define_macros, extra_compile_args and + extra_link_args are extended with an output of pkg-config for libfoo and + libbar. + + Raises PkgConfigError in case the pkg-config call fails. + """ + + def get_include_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-I")] + + def get_library_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-L")] + + def get_libraries(string): + return [x[2:] for x in string.split() if x.startswith("-l")] + + # convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by distutils + def get_macros(string): + def _macro(x): + x = x[2:] # drop "-D" + if '=' in x: + return tuple(x.split("=", 1)) # "-Dfoo=bar" => ("foo", "bar") + else: + return (x, None) # "-Dfoo" => ("foo", None) + return [_macro(x) for x in string.split() if x.startswith("-D")] + + def get_other_cflags(string): + return [x for x in string.split() if not x.startswith("-I") and + not x.startswith("-D")] + + def get_other_libs(string): + return [x for x in string.split() if not x.startswith("-L") and + not x.startswith("-l")] + + # return kwargs for given libname + def kwargs(libname): + fse = sys.getfilesystemencoding() + all_cflags = call(libname, "--cflags") + all_libs = call(libname, "--libs") + return { + "include_dirs": get_include_dirs(all_cflags), + "library_dirs": get_library_dirs(all_libs), + "libraries": get_libraries(all_libs), + "define_macros": get_macros(all_cflags), + "extra_compile_args": get_other_cflags(all_cflags), + "extra_link_args": get_other_libs(all_libs), + } + + # merge all arguments together + ret = {} + for libname in libs: + lib_flags = kwargs(libname) + merge_flags(ret, lib_flags) + return ret diff --git a/cffi/recompiler.py b/cffi/recompiler.py new file mode 100644 index 0000000..20e912b --- /dev/null +++ b/cffi/recompiler.py @@ -0,0 +1,1542 @@ +import os, sys, io +from . import ffiplatform, model +from .error import VerificationError +from .cffi_opcode import * + +VERSION_BASE = 0x2601 +VERSION_EMBEDDED = 0x2701 +VERSION_CHAR16CHAR32 = 0x2801 + + +class GlobalExpr: + def __init__(self, name, address, type_op, size=0, check_value=0): + self.name = name + self.address = address + self.type_op = type_op + self.size = size + self.check_value = check_value + + def as_c_expr(self): + return ' { "%s", (void *)%s, %s, (void *)%s },' % ( + self.name, self.address, self.type_op.as_c_expr(), self.size) + + def as_python_expr(self): + return "b'%s%s',%d" % (self.type_op.as_python_bytes(), self.name, + self.check_value) + +class FieldExpr: + def __init__(self, name, field_offset, field_size, fbitsize, field_type_op): + self.name = name + self.field_offset = field_offset + self.field_size = field_size + self.fbitsize = fbitsize + self.field_type_op = field_type_op + + def as_c_expr(self): + spaces = " " * len(self.name) + return (' { "%s", %s,\n' % (self.name, self.field_offset) + + ' %s %s,\n' % (spaces, self.field_size) + + ' %s %s },' % (spaces, self.field_type_op.as_c_expr())) + + def as_python_expr(self): + raise NotImplementedError + + def as_field_python_expr(self): + if self.field_type_op.op == OP_NOOP: + size_expr = '' + elif self.field_type_op.op == OP_BITFIELD: + size_expr = format_four_bytes(self.fbitsize) + else: + raise NotImplementedError + return "b'%s%s%s'" % (self.field_type_op.as_python_bytes(), + size_expr, + self.name) + +class StructUnionExpr: + def __init__(self, name, type_index, flags, size, alignment, comment, + first_field_index, c_fields): + self.name = name + self.type_index = type_index + self.flags = flags + self.size = size + self.alignment = alignment + self.comment = comment + self.first_field_index = first_field_index + self.c_fields = c_fields + + def as_c_expr(self): + return (' { "%s", %d, %s,' % (self.name, self.type_index, self.flags) + + '\n %s, %s, ' % (self.size, self.alignment) + + '%d, %d ' % (self.first_field_index, len(self.c_fields)) + + ('/* %s */ ' % self.comment if self.comment else '') + + '},') + + def as_python_expr(self): + flags = eval(self.flags, G_FLAGS) + fields_expr = [c_field.as_field_python_expr() + for c_field in self.c_fields] + return "(b'%s%s%s',%s)" % ( + format_four_bytes(self.type_index), + format_four_bytes(flags), + self.name, + ','.join(fields_expr)) + +class EnumExpr: + def __init__(self, name, type_index, size, signed, allenums): + self.name = name + self.type_index = type_index + self.size = size + self.signed = signed + self.allenums = allenums + + def as_c_expr(self): + return (' { "%s", %d, _cffi_prim_int(%s, %s),\n' + ' "%s" },' % (self.name, self.type_index, + self.size, self.signed, self.allenums)) + + def as_python_expr(self): + prim_index = { + (1, 0): PRIM_UINT8, (1, 1): PRIM_INT8, + (2, 0): PRIM_UINT16, (2, 1): PRIM_INT16, + (4, 0): PRIM_UINT32, (4, 1): PRIM_INT32, + (8, 0): PRIM_UINT64, (8, 1): PRIM_INT64, + }[self.size, self.signed] + return "b'%s%s%s\\x00%s'" % (format_four_bytes(self.type_index), + format_four_bytes(prim_index), + self.name, self.allenums) + +class TypenameExpr: + def __init__(self, name, type_index): + self.name = name + self.type_index = type_index + + def as_c_expr(self): + return ' { "%s", %d },' % (self.name, self.type_index) + + def as_python_expr(self): + return "b'%s%s'" % (format_four_bytes(self.type_index), self.name) + + +# ____________________________________________________________ + + +class Recompiler: + _num_externpy = 0 + + def __init__(self, ffi, module_name, target_is_python=False): + self.ffi = ffi + self.module_name = module_name + self.target_is_python = target_is_python + self._version = VERSION_BASE + + def needs_version(self, ver): + self._version = max(self._version, ver) + + def collect_type_table(self): + self._typesdict = {} + self._generate("collecttype") + # + all_decls = sorted(self._typesdict, key=str) + # + # prepare all FUNCTION bytecode sequences first + self.cffi_types = [] + for tp in all_decls: + if tp.is_raw_function: + assert self._typesdict[tp] is None + self._typesdict[tp] = len(self.cffi_types) + self.cffi_types.append(tp) # placeholder + for tp1 in tp.args: + assert isinstance(tp1, (model.VoidType, + model.BasePrimitiveType, + model.PointerType, + model.StructOrUnionOrEnum, + model.FunctionPtrType)) + if self._typesdict[tp1] is None: + self._typesdict[tp1] = len(self.cffi_types) + self.cffi_types.append(tp1) # placeholder + self.cffi_types.append('END') # placeholder + # + # prepare all OTHER bytecode sequences + for tp in all_decls: + if not tp.is_raw_function and self._typesdict[tp] is None: + self._typesdict[tp] = len(self.cffi_types) + self.cffi_types.append(tp) # placeholder + if tp.is_array_type and tp.length is not None: + self.cffi_types.append('LEN') # placeholder + assert None not in self._typesdict.values() + # + # collect all structs and unions and enums + self._struct_unions = {} + self._enums = {} + for tp in all_decls: + if isinstance(tp, model.StructOrUnion): + self._struct_unions[tp] = None + elif isinstance(tp, model.EnumType): + self._enums[tp] = None + for i, tp in enumerate(sorted(self._struct_unions, + key=lambda tp: tp.name)): + self._struct_unions[tp] = i + for i, tp in enumerate(sorted(self._enums, + key=lambda tp: tp.name)): + self._enums[tp] = i + # + # emit all bytecode sequences now + for tp in all_decls: + method = getattr(self, '_emit_bytecode_' + tp.__class__.__name__) + method(tp, self._typesdict[tp]) + # + # consistency check + for op in self.cffi_types: + assert isinstance(op, CffiOp) + self.cffi_types = tuple(self.cffi_types) # don't change any more + + def _do_collect_type(self, tp): + if not isinstance(tp, model.BaseTypeByIdentity): + if isinstance(tp, tuple): + for x in tp: + self._do_collect_type(x) + return + if tp not in self._typesdict: + self._typesdict[tp] = None + if isinstance(tp, model.FunctionPtrType): + self._do_collect_type(tp.as_raw_function()) + elif isinstance(tp, model.StructOrUnion): + if tp.fldtypes is not None and ( + tp not in self.ffi._parser._included_declarations): + for name1, tp1, _, _ in tp.enumfields(): + self._do_collect_type(self._field_type(tp, name1, tp1)) + else: + for _, x in tp._get_items(): + self._do_collect_type(x) + + def _generate(self, step_name): + lst = self.ffi._parser._declarations.items() + for name, (tp, quals) in sorted(lst): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in recompile(): %r" % name) + try: + self._current_quals = quals + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + # ---------- + + ALL_STEPS = ["global", "field", "struct_union", "enum", "typename"] + + def collect_step_tables(self): + # collect the declarations for '_cffi_globals', '_cffi_typenames', etc. + self._lsts = {} + for step_name in self.ALL_STEPS: + self._lsts[step_name] = [] + self._seen_struct_unions = set() + self._generate("ctx") + self._add_missing_struct_unions() + # + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + if step_name != "field": + lst.sort(key=lambda entry: entry.name) + self._lsts[step_name] = tuple(lst) # don't change any more + # + # check for a possible internal inconsistency: _cffi_struct_unions + # should have been generated with exactly self._struct_unions + lst = self._lsts["struct_union"] + for tp, i in self._struct_unions.items(): + assert i < len(lst) + assert lst[i].name == tp.name + assert len(lst) == len(self._struct_unions) + # same with enums + lst = self._lsts["enum"] + for tp, i in self._enums.items(): + assert i < len(lst) + assert lst[i].name == tp.name + assert len(lst) == len(self._enums) + + # ---------- + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def write_source_to_f(self, f, preamble): + if self.target_is_python: + assert preamble is None + self.write_py_source_to_f(f) + else: + assert preamble is not None + self.write_c_source_to_f(f, preamble) + + def _rel_readlines(self, filename): + g = open(os.path.join(os.path.dirname(__file__), filename), 'r') + lines = g.readlines() + g.close() + return lines + + def write_c_source_to_f(self, f, preamble): + self._f = f + prnt = self._prnt + if self.ffi._embedding is not None: + prnt('#define _CFFI_USE_EMBEDDING') + # + # first the '#include' (actually done by inlining the file's content) + lines = self._rel_readlines('_cffi_include.h') + i = lines.index('#include "parse_c_type.h"\n') + lines[i:i+1] = self._rel_readlines('parse_c_type.h') + prnt(''.join(lines)) + # + # if we have ffi._embedding != None, we give it here as a macro + # and include an extra file + base_module_name = self.module_name.split('.')[-1] + if self.ffi._embedding is not None: + prnt('#define _CFFI_MODULE_NAME "%s"' % (self.module_name,)) + prnt('static const char _CFFI_PYTHON_STARTUP_CODE[] = {') + self._print_string_literal_in_array(self.ffi._embedding) + prnt('0 };') + prnt('#ifdef PYPY_VERSION') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC _cffi_pypyinit_%s' % ( + base_module_name,)) + prnt('#elif PY_MAJOR_VERSION >= 3') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC PyInit_%s' % ( + base_module_name,)) + prnt('#else') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC init%s' % ( + base_module_name,)) + prnt('#endif') + lines = self._rel_readlines('_embedding.h') + i = lines.index('#include "_cffi_errors.h"\n') + lines[i:i+1] = self._rel_readlines('_cffi_errors.h') + prnt(''.join(lines)) + self.needs_version(VERSION_EMBEDDED) + # + # then paste the C source given by the user, verbatim. + prnt('/************************************************************/') + prnt() + prnt(preamble) + prnt() + prnt('/************************************************************/') + prnt() + # + # the declaration of '_cffi_types' + prnt('static void *_cffi_types[] = {') + typeindex2type = dict([(i, tp) for (tp, i) in self._typesdict.items()]) + for i, op in enumerate(self.cffi_types): + comment = '' + if i in typeindex2type: + comment = ' // ' + typeindex2type[i]._get_c_name() + prnt('/* %2d */ %s,%s' % (i, op.as_c_expr(), comment)) + if not self.cffi_types: + prnt(' 0') + prnt('};') + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._seen_constants = set() + self._generate("decl") + # + # the declaration of '_cffi_globals' and '_cffi_typenames' + nums = {} + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + nums[step_name] = len(lst) + if nums[step_name] > 0: + prnt('static const struct _cffi_%s_s _cffi_%ss[] = {' % ( + step_name, step_name)) + for entry in lst: + prnt(entry.as_c_expr()) + prnt('};') + prnt() + # + # the declaration of '_cffi_includes' + if self.ffi._included_ffis: + prnt('static const char * const _cffi_includes[] = {') + for ffi_to_include in self.ffi._included_ffis: + try: + included_module_name, included_source = ( + ffi_to_include._assigned_source[:2]) + except AttributeError: + raise VerificationError( + "ffi object %r includes %r, but the latter has not " + "been prepared with set_source()" % ( + self.ffi, ffi_to_include,)) + if included_source is None: + raise VerificationError( + "not implemented yet: ffi.include() of a Python-based " + "ffi inside a C-based ffi") + prnt(' "%s",' % (included_module_name,)) + prnt(' NULL') + prnt('};') + prnt() + # + # the declaration of '_cffi_type_context' + prnt('static const struct _cffi_type_context_s _cffi_type_context = {') + prnt(' _cffi_types,') + for step_name in self.ALL_STEPS: + if nums[step_name] > 0: + prnt(' _cffi_%ss,' % step_name) + else: + prnt(' NULL, /* no %ss */' % step_name) + for step_name in self.ALL_STEPS: + if step_name != "field": + prnt(' %d, /* num_%ss */' % (nums[step_name], step_name)) + if self.ffi._included_ffis: + prnt(' _cffi_includes,') + 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" + prnt(' %d, /* flags */' % flags) + prnt('};') + prnt() + # + # the init function + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility push(default) /* for -fvisibility= */') + prnt('#endif') + prnt() + prnt('#ifdef PYPY_VERSION') + prnt('PyMODINIT_FUNC') + prnt('_cffi_pypyinit_%s(const void *p[])' % (base_module_name,)) + prnt('{') + if self._num_externpy: + prnt(' if (((intptr_t)p[0]) >= 0x0A03) {') + prnt(' _cffi_call_python_org = ' + '(void(*)(struct _cffi_externpy_s *, char *))p[1];') + prnt(' }') + prnt(' p[0] = (const void *)0x%x;' % self._version) + prnt(' p[1] = &_cffi_type_context;') + prnt('#if PY_MAJOR_VERSION >= 3') + prnt(' return NULL;') + prnt('#endif') + prnt('}') + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + prnt('# ifdef _MSC_VER') + prnt(' PyMODINIT_FUNC') + prnt('# if PY_MAJOR_VERSION >= 3') + prnt(' PyInit_%s(void) { return NULL; }' % (base_module_name,)) + prnt('# else') + prnt(' init%s(void) { }' % (base_module_name,)) + prnt('# endif') + prnt('# endif') + prnt('#elif PY_MAJOR_VERSION >= 3') + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % (base_module_name,)) + prnt('{') + prnt(' return _cffi_init("%s", 0x%x, &_cffi_type_context);' % ( + self.module_name, self._version)) + prnt('}') + prnt('#else') + prnt('PyMODINIT_FUNC') + prnt('init%s(void)' % (base_module_name,)) + prnt('{') + prnt(' _cffi_init("%s", 0x%x, &_cffi_type_context);' % ( + self.module_name, self._version)) + prnt('}') + prnt('#endif') + prnt() + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility pop') + prnt('#endif') + self._version = None + + def _to_py(self, x): + if isinstance(x, str): + return "b'%s'" % (x,) + if isinstance(x, (list, tuple)): + rep = [self._to_py(item) for item in x] + if len(rep) == 1: + rep.append('') + return "(%s)" % (','.join(rep),) + return x.as_python_expr() # Py2: unicode unexpected; Py3: bytes unexp. + + def write_py_source_to_f(self, f): + self._f = f + prnt = self._prnt + # + # header + prnt("# auto-generated file") + prnt("import _cffi_backend") + # + # the 'import' of the included ffis + num_includes = len(self.ffi._included_ffis or ()) + for i in range(num_includes): + ffi_to_include = self.ffi._included_ffis[i] + try: + included_module_name, included_source = ( + ffi_to_include._assigned_source[:2]) + except AttributeError: + raise VerificationError( + "ffi object %r includes %r, but the latter has not " + "been prepared with set_source()" % ( + self.ffi, ffi_to_include,)) + if included_source is not None: + raise VerificationError( + "not implemented yet: ffi.include() of a C-based " + "ffi inside a Python-based ffi") + prnt('from %s import ffi as _ffi%d' % (included_module_name, i)) + prnt() + prnt("ffi = _cffi_backend.FFI('%s'," % (self.module_name,)) + prnt(" _version = 0x%x," % (self._version,)) + self._version = None + # + # the '_types' keyword argument + self.cffi_types = tuple(self.cffi_types) # don't change any more + types_lst = [op.as_python_bytes() for op in self.cffi_types] + prnt(' _types = %s,' % (self._to_py(''.join(types_lst)),)) + typeindex2type = dict([(i, tp) for (tp, i) in self._typesdict.items()]) + # + # the keyword arguments from ALL_STEPS + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + if len(lst) > 0 and step_name != "field": + prnt(' _%ss = %s,' % (step_name, self._to_py(lst))) + # + # the '_includes' keyword argument + if num_includes > 0: + prnt(' _includes = (%s,),' % ( + ', '.join(['_ffi%d' % i for i in range(num_includes)]),)) + # + # the footer + prnt(')') + + # ---------- + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.BasePrimitiveType) and not tp.is_complex_type(): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + elif isinstance(tp, model.UnknownFloatType): + # don't check with is_float_type(): it may be a 'long + # double' here, and _cffi_to_c_double would loose precision + converter = '(%s)_cffi_to_c_double' % (tp.get_c_name(''),) + else: + cname = tp.get_c_name('') + converter = '(%s)_cffi_to_c_%s' % (cname, + tp.name.replace(' ', '_')) + if cname in ('char16_t', 'char32_t'): + self.needs_version(VERSION_CHAR16CHAR32) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif (isinstance(tp, model.StructOrUnionOrEnum) or + isinstance(tp, model.BasePrimitiveType)): + # a struct (not a struct pointer) as a function argument; + # or, a complex (the same code works) + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' if (datasize < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' %s = (%s)alloca((size_t)datasize);' % ( + tovar, tp.get_c_name(''))) + self._prnt(' memset((void *)%s, 0, (size_t)datasize);' % (tovar,)) + self._prnt(' if (_cffi_convert_array_from_object(' + '(char *)%s, _cffi_type(%d), %s) < 0)' % ( + tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.BasePrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif isinstance(tp, model.UnknownFloatType): + return '_cffi_from_c_double(%s)' % (var,) + elif tp.name != 'long double' and not tp.is_complex_type(): + cname = tp.name.replace(' ', '_') + if cname in ('char16_t', 'char32_t'): + self.needs_version(VERSION_CHAR16CHAR32) + return '_cffi_from_c_%s(%s)' % (cname, var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs + + def _typedef_type(self, tp, name): + return self._global_type(tp, "(*(%s *)0)" % (name,)) + + def _generate_cpy_typedef_collecttype(self, tp, name): + self._do_collect_type(self._typedef_type(tp, name)) + + def _generate_cpy_typedef_decl(self, tp, name): + pass + + def _typedef_ctx(self, tp, name): + type_index = self._typesdict[tp] + self._lsts["typename"].append(TypenameExpr(name, type_index)) + + def _generate_cpy_typedef_ctx(self, tp, name): + tp = self._typedef_type(tp, name) + self._typedef_ctx(tp, name) + if getattr(tp, "origin", None) == "unknown_type": + self._struct_ctx(tp, tp.name, approxname=None) + elif isinstance(tp, model.NamedPointerType): + self._struct_ctx(tp.totype, tp.totype.name, approxname=tp.name, + named_ptr=tp) + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + self._do_collect_type(tp.as_raw_function()) + if tp.ellipsis and not self.target_is_python: + self._do_collect_type(tp) + + def _generate_cpy_function_decl(self, tp, name): + assert not self.target_is_python + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_constant_decl(tp, name) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + # + # ------------------------------ + # the 'd' version of the function, only for addressof(lib, 'func') + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arguments.append(type.get_c_name(' x%d' % i, context)) + call_arguments.append('x%d' % i) + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + if tp.abi: + abi = tp.abi + ' ' + else: + abi = '' + name_and_arguments = '%s_cffi_d_%s(%s)' % (abi, name, repr_arguments) + prnt('static %s' % (tp.result.get_c_name(name_and_arguments),)) + prnt('{') + call_arguments = ', '.join(call_arguments) + result_code = 'return ' + if isinstance(tp.result, model.VoidType): + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, call_arguments)) + prnt('}') + # + prnt('#ifndef PYPY_VERSION') # ------------------------------ + # + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arg = type.get_c_name(' x%d' % i, context) + prnt(' %s;' % arg) + # + localvars = set() + for type in tp.args: + self._extra_local_variables(type, localvars) + for decl in localvars: + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + result_decl = ' %s;' % tp.result.get_c_name(' result', context) + prnt(result_decl) + else: + result_decl = None + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_UnpackTuple(args, "%s", %d, %d, %s))' % ( + name, len(rng), len(rng), + ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + call_arguments = ['x%d' % i for i in range(len(tp.args))] + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' return %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + else: + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + # + prnt('#else') # ------------------------------ + # + # the PyPy version: need to replace struct/union arguments with + # pointers, and if the result is a struct/union, insert a first + # arg that is a pointer to the result. We also do that for + # complex args and return type. + def need_indirection(type): + return (isinstance(type, model.StructOrUnion) or + (isinstance(type, model.PrimitiveType) and + type.is_complex_type())) + difference = False + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + indirection = '' + if need_indirection(type): + indirection = '*' + difference = True + arg = type.get_c_name(' %sx%d' % (indirection, i), context) + arguments.append(arg) + call_arguments.append('%sx%d' % (indirection, i)) + tp_result = tp.result + if need_indirection(tp_result): + context = 'result of %s' % name + arg = tp_result.get_c_name(' *result', context) + arguments.insert(0, arg) + tp_result = model.void_type + result_decl = None + result_code = '*result = ' + difference = True + if difference: + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '%s_cffi_f_%s(%s)' % (abi, name, + repr_arguments) + prnt('static %s' % (tp_result.get_c_name(name_and_arguments),)) + prnt('{') + if result_decl: + prnt(result_decl) + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + if result_decl: + prnt(' return result;') + prnt('}') + else: + prnt('# define _cffi_f_%s _cffi_d_%s' % (name, name)) + # + prnt('#endif') # ------------------------------ + prnt() + + def _generate_cpy_function_ctx(self, tp, name): + if tp.ellipsis and not self.target_is_python: + self._generate_cpy_constant_ctx(tp, name) + return + type_index = self._typesdict[tp.as_raw_function()] + numargs = len(tp.args) + if self.target_is_python: + meth_kind = OP_DLOPEN_FUNC + elif numargs == 0: + meth_kind = OP_CPYTHON_BLTN_N # 'METH_NOARGS' + elif numargs == 1: + meth_kind = OP_CPYTHON_BLTN_O # 'METH_O' + else: + meth_kind = OP_CPYTHON_BLTN_V # 'METH_VARARGS' + self._lsts["global"].append( + GlobalExpr(name, '_cffi_f_%s' % name, + CffiOp(meth_kind, type_index), + size='_cffi_d_%s' % name)) + + # ---------- + # named structs or unions + + def _field_type(self, tp_struct, field_name, tp_field): + if isinstance(tp_field, model.ArrayType): + actual_length = tp_field.length + if actual_length == '...': + ptr_struct_name = tp_struct.get_c_name('*') + actual_length = '_cffi_array_len(((%s)0)->%s)' % ( + ptr_struct_name, field_name) + tp_item = self._field_type(tp_struct, '%s[0]' % field_name, + tp_field.item) + tp_field = model.ArrayType(tp_item, actual_length) + return tp_field + + def _struct_collecttype(self, tp): + self._do_collect_type(tp) + if self.target_is_python: + # also requires nested anon struct/unions in ABI mode, recursively + for fldtype in tp.anonymous_struct_fields(): + self._struct_collecttype(fldtype) + + def _struct_decl(self, tp, cname, approxname): + if tp.fldtypes is None: + return + prnt = self._prnt + checkfuncname = '_cffi_checkfld_%s' % (approxname,) + prnt('_CFFI_UNUSED_FN') + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + try: + if ftype.is_integer_type() or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(" (void)((p->%s) | 0); /* check that '%s.%s' is " + "an integer */" % (fname, cname, fname)) + continue + # only accept exactly the type declared, except that '[]' + # is interpreted as a '*' and so will match any array length. + # (It would also match '*', but that's harder to detect...) + while (isinstance(ftype, model.ArrayType) + and (ftype.length is None or ftype.length == '...')): + ftype = ftype.item + fname = fname + '[0]' + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('struct _cffi_align_%s { char x; %s y; };' % (approxname, cname)) + prnt() + + def _struct_ctx(self, tp, cname, approxname, named_ptr=None): + type_index = self._typesdict[tp] + reason_for_not_expanding = None + flags = [] + if isinstance(tp, model.UnionType): + flags.append("_CFFI_F_UNION") + if tp.fldtypes is None: + flags.append("_CFFI_F_OPAQUE") + reason_for_not_expanding = "opaque" + if (tp not in self.ffi._parser._included_declarations and + (named_ptr is None or + named_ptr not in self.ffi._parser._included_declarations)): + if tp.fldtypes is None: + pass # opaque + elif tp.partial or any(tp.anonymous_struct_fields()): + pass # field layout obtained silently from the C compiler + else: + flags.append("_CFFI_F_CHECK_FIELDS") + if tp.packed: + if tp.packed > 1: + raise NotImplementedError( + "%r is declared with 'pack=%r'; only 0 or 1 are " + "supported in API mode (try to use \"...;\", which " + "does not require a 'pack' declaration)" % + (tp, tp.packed)) + flags.append("_CFFI_F_PACKED") + else: + flags.append("_CFFI_F_EXTERNAL") + reason_for_not_expanding = "external" + flags = '|'.join(flags) or '0' + c_fields = [] + if reason_for_not_expanding is None: + expand_anonymous_struct_union = not self.target_is_python + enumfields = list(tp.enumfields(expand_anonymous_struct_union)) + for fldname, fldtype, fbitsize, fqual in enumfields: + fldtype = self._field_type(tp, fldname, fldtype) + self._check_not_opaque(fldtype, + "field '%s.%s'" % (tp.name, fldname)) + # cname is None for _add_missing_struct_unions() only + op = OP_NOOP + if fbitsize >= 0: + op = OP_BITFIELD + size = '%d /* bits */' % fbitsize + elif cname is None or ( + isinstance(fldtype, model.ArrayType) and + fldtype.length is None): + size = '(size_t)-1' + else: + size = 'sizeof(((%s)0)->%s)' % ( + tp.get_c_name('*') if named_ptr is None + else named_ptr.name, + fldname) + if cname is None or fbitsize >= 0: + offset = '(size_t)-1' + elif named_ptr is not None: + offset = '((char *)&((%s)0)->%s) - (char *)0' % ( + named_ptr.name, fldname) + else: + offset = 'offsetof(%s, %s)' % (tp.get_c_name(''), fldname) + c_fields.append( + FieldExpr(fldname, offset, size, fbitsize, + CffiOp(op, self._typesdict[fldtype]))) + first_field_index = len(self._lsts["field"]) + self._lsts["field"].extend(c_fields) + # + if cname is None: # unknown name, for _add_missing_struct_unions + size = '(size_t)-2' + align = -2 + comment = "unnamed" + else: + if named_ptr is not None: + size = 'sizeof(*(%s)0)' % (named_ptr.name,) + align = '-1 /* unknown alignment */' + else: + size = 'sizeof(%s)' % (cname,) + align = 'offsetof(struct _cffi_align_%s, y)' % (approxname,) + comment = None + else: + size = '(size_t)-1' + align = -1 + first_field_index = -1 + comment = reason_for_not_expanding + self._lsts["struct_union"].append( + StructUnionExpr(tp.name, type_index, flags, size, align, comment, + first_field_index, c_fields)) + self._seen_struct_unions.add(tp) + + def _check_not_opaque(self, tp, location): + while isinstance(tp, model.ArrayType): + tp = tp.item + if isinstance(tp, model.StructOrUnion) and tp.fldtypes is None: + raise TypeError( + "%s is of an opaque type (not declared in cdef())" % location) + + def _add_missing_struct_unions(self): + # not very nice, but some struct declarations might be missing + # because they don't have any known C name. Check that they are + # not partial (we can't complete or verify them!) and emit them + # anonymously. + lst = list(self._struct_unions.items()) + lst.sort(key=lambda tp_order: tp_order[1]) + for tp, order in lst: + if tp not in self._seen_struct_unions: + if tp.partial: + raise NotImplementedError("internal inconsistency: %r is " + "partial but was not seen at " + "this point" % (tp,)) + if tp.name.startswith('$') and tp.name[1:].isdigit(): + approxname = tp.name[1:] + elif tp.name == '_IO_FILE' and tp.forcename == 'FILE': + approxname = 'FILE' + self._typedef_ctx(tp, 'FILE') + else: + raise NotImplementedError("internal inconsistency: %r" % + (tp,)) + self._struct_ctx(tp, None, approxname) + + def _generate_cpy_struct_collecttype(self, tp, name): + self._struct_collecttype(tp) + _generate_cpy_union_collecttype = _generate_cpy_struct_collecttype + + def _struct_names(self, tp): + cname = tp.get_c_name('') + if ' ' in cname: + return cname, cname.replace(' ', '_') + else: + return cname, '_' + cname + + def _generate_cpy_struct_decl(self, tp, name): + self._struct_decl(tp, *self._struct_names(tp)) + _generate_cpy_union_decl = _generate_cpy_struct_decl + + def _generate_cpy_struct_ctx(self, tp, name): + self._struct_ctx(tp, *self._struct_names(tp)) + _generate_cpy_union_ctx = _generate_cpy_struct_ctx + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_cpy_anonymous_collecttype(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_collecttype(tp, name) + else: + self._struct_collecttype(tp) + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp) + else: + self._struct_decl(tp, name, 'typedef_' + name) + + def _generate_cpy_anonymous_ctx(self, tp, name): + if isinstance(tp, model.EnumType): + self._enum_ctx(tp, name) + else: + self._struct_ctx(tp, name, 'typedef_' + name) + + # ---------- + # constants, declared with "static const ..." + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + check_value=None): + if (category, name) in self._seen_constants: + raise VerificationError( + "duplicate declaration of %s '%s'" % (category, name)) + self._seen_constants.add((category, name)) + # + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + if is_int: + prnt('static int %s(unsigned long long *o)' % funcname) + prnt('{') + prnt(' int n = (%s) <= 0;' % (name,)) + prnt(' *o = (unsigned long long)((%s) | 0);' + ' /* check that %s is an integer */' % (name, name)) + if check_value is not None: + if check_value > 0: + check_value = '%dU' % (check_value,) + prnt(' if (!_cffi_check_int(*o, n, %s))' % (check_value,)) + prnt(' n |= 2;') + prnt(' return n;') + prnt('}') + else: + assert check_value is None + prnt('static void %s(char *o)' % funcname) + prnt('{') + prnt(' *(%s)o = %s;' % (tp.get_c_name('*'), name)) + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = tp.is_integer_type() + if not is_int or self.target_is_python: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + def _generate_cpy_constant_ctx(self, tp, name): + if not self.target_is_python and tp.is_integer_type(): + type_op = CffiOp(OP_CONSTANT_INT, -1) + else: + if self.target_is_python: + const_kind = OP_DLOPEN_CONST + else: + const_kind = OP_CONSTANT + type_index = self._typesdict[tp] + type_op = CffiOp(const_kind, type_index) + self._lsts["global"].append( + GlobalExpr(name, '_cffi_const_%s' % name, type_op)) + + # ---------- + # enums + + def _generate_cpy_enum_collecttype(self, tp, name): + self._do_collect_type(tp) + + def _generate_cpy_enum_decl(self, tp, name=None): + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator) + + def _enum_ctx(self, tp, cname): + type_index = self._typesdict[tp] + type_op = CffiOp(OP_ENUM, -1) + if self.target_is_python: + tp.check_not_partial() + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._lsts["global"].append( + GlobalExpr(enumerator, '_cffi_const_%s' % enumerator, type_op, + check_value=enumvalue)) + # + if cname is not None and '$' not in cname and not self.target_is_python: + size = "sizeof(%s)" % cname + signed = "((%s)-1) <= 0" % cname + else: + basetp = tp.build_baseinttype(self.ffi, []) + size = self.ffi.sizeof(basetp) + signed = int(int(self.ffi.cast(basetp, -1)) < 0) + allenums = ",".join(tp.enumerators) + self._lsts["enum"].append( + EnumExpr(tp.name, type_index, size, signed, allenums)) + + def _generate_cpy_enum_ctx(self, tp, name): + self._enum_ctx(tp, tp._get_c_name()) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_collecttype(self, tp, name): + pass + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + def _generate_cpy_macro_ctx(self, tp, name): + if tp == '...': + if self.target_is_python: + raise VerificationError( + "cannot use the syntax '...' in '#define %s ...' when " + "using the ABI mode" % (name,)) + check_value = None + else: + check_value = tp # an integer + type_op = CffiOp(OP_CONSTANT_INT, -1) + self._lsts["global"].append( + GlobalExpr(name, '_cffi_const_%s' % name, type_op, + check_value=check_value)) + + # ---------- + # global variables + + def _global_type(self, tp, global_name): + if isinstance(tp, model.ArrayType): + actual_length = tp.length + if actual_length == '...': + actual_length = '_cffi_array_len(%s)' % (global_name,) + tp_item = self._global_type(tp.item, '%s[0]' % global_name) + tp = model.ArrayType(tp_item, actual_length) + return tp + + def _generate_cpy_variable_collecttype(self, tp, name): + self._do_collect_type(self._global_type(tp, name)) + + def _generate_cpy_variable_decl(self, tp, name): + prnt = self._prnt + tp = self._global_type(tp, name) + if isinstance(tp, model.ArrayType) and tp.length is None: + tp = tp.item + ampersand = '' + else: + ampersand = '&' + # This code assumes that casts from "tp *" to "void *" is a + # no-op, i.e. a function that returns a "tp *" can be called + # as if it returned a "void *". This should be generally true + # on any modern machine. The only exception to that rule (on + # uncommon architectures, and as far as I can tell) might be + # if 'tp' were a function type, but that is not possible here. + # (If 'tp' is a function _pointer_ type, then casts from "fn_t + # **" to "void *" are again no-ops, as far as I can tell.) + decl = '*_cffi_var_%s(void)' % (name,) + prnt('static ' + tp.get_c_name(decl, quals=self._current_quals)) + prnt('{') + prnt(' return %s(%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_cpy_variable_ctx(self, tp, name): + tp = self._global_type(tp, name) + type_index = self._typesdict[tp] + if self.target_is_python: + op = OP_GLOBAL_VAR + else: + op = OP_GLOBAL_VAR_F + self._lsts["global"].append( + GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index))) + + # ---------- + # extern "Python" + + def _generate_cpy_extern_python_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + self._do_collect_type(tp) + _generate_cpy_dllexport_python_collecttype = \ + _generate_cpy_extern_python_plus_c_collecttype = \ + _generate_cpy_extern_python_collecttype + + def _extern_python_decl(self, tp, name, tag_and_space): + prnt = self._prnt + if isinstance(tp.result, model.VoidType): + size_of_result = '0' + else: + context = 'result of %s' % name + size_of_result = '(int)sizeof(%s)' % ( + tp.result.get_c_name('', context),) + prnt('static struct _cffi_externpy_s _cffi_externpy__%s =' % name) + prnt(' { "%s.%s", %s };' % (self.module_name, name, size_of_result)) + prnt() + # + arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arg = type.get_c_name(' a%d' % i, context) + arguments.append(arg) + # + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '%s(%s)' % (name, repr_arguments) + if tp.abi == "__stdcall": + name_and_arguments = '_cffi_stdcall ' + name_and_arguments + # + def may_need_128_bits(tp): + return (isinstance(tp, model.PrimitiveType) and + tp.name == 'long double') + # + size_of_a = max(len(tp.args)*8, 8) + if may_need_128_bits(tp.result): + size_of_a = max(size_of_a, 16) + if isinstance(tp.result, model.StructOrUnion): + size_of_a = 'sizeof(%s) > %d ? sizeof(%s) : %d' % ( + tp.result.get_c_name(''), size_of_a, + tp.result.get_c_name(''), size_of_a) + prnt('%s%s' % (tag_and_space, tp.result.get_c_name(name_and_arguments))) + prnt('{') + prnt(' char a[%s];' % size_of_a) + prnt(' char *p = a;') + for i, type in enumerate(tp.args): + arg = 'a%d' % i + if (isinstance(type, model.StructOrUnion) or + may_need_128_bits(type)): + arg = '&' + arg + type = model.PointerType(type) + prnt(' *(%s)(p + %d) = %s;' % (type.get_c_name('*'), i*8, arg)) + prnt(' _cffi_call_python(&_cffi_externpy__%s, p);' % name) + if not isinstance(tp.result, model.VoidType): + prnt(' return *(%s)p;' % (tp.result.get_c_name('*'),)) + prnt('}') + prnt() + self._num_externpy += 1 + + def _generate_cpy_extern_python_decl(self, tp, name): + self._extern_python_decl(tp, name, 'static ') + + def _generate_cpy_dllexport_python_decl(self, tp, name): + self._extern_python_decl(tp, name, 'CFFI_DLLEXPORT ') + + def _generate_cpy_extern_python_plus_c_decl(self, tp, name): + self._extern_python_decl(tp, name, '') + + def _generate_cpy_extern_python_ctx(self, tp, name): + if self.target_is_python: + raise VerificationError( + "cannot use 'extern \"Python\"' in the ABI mode") + if tp.ellipsis: + raise NotImplementedError("a vararg function is extern \"Python\"") + type_index = self._typesdict[tp] + type_op = CffiOp(OP_EXTERN_PYTHON, type_index) + self._lsts["global"].append( + GlobalExpr(name, '&_cffi_externpy__%s' % name, type_op, name)) + + _generate_cpy_dllexport_python_ctx = \ + _generate_cpy_extern_python_plus_c_ctx = \ + _generate_cpy_extern_python_ctx + + def _print_string_literal_in_array(self, s): + prnt = self._prnt + prnt('// # NB. this is not a string because of a size limit in MSVC') + for line in s.splitlines(True): + prnt(('// ' + line).rstrip()) + printed_line = '' + for c in line: + if len(printed_line) >= 76: + prnt(printed_line) + printed_line = '' + printed_line += '%d,' % (ord(c),) + prnt(printed_line) + + # ---------- + # emitting the opcodes for individual types + + def _emit_bytecode_VoidType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, PRIM_VOID) + + def _emit_bytecode_PrimitiveType(self, tp, index): + prim_index = PRIMITIVE_TO_INDEX[tp.name] + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, prim_index) + + def _emit_bytecode_UnknownIntegerType(self, tp, index): + s = ('_cffi_prim_int(sizeof(%s), (\n' + ' ((%s)-1) | 0 /* check that %s is an integer type */\n' + ' ) <= 0)' % (tp.name, tp.name, tp.name)) + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, s) + + def _emit_bytecode_UnknownFloatType(self, tp, index): + s = ('_cffi_prim_float(sizeof(%s) *\n' + ' (((%s)1) / 2) * 2 /* integer => 0, float => 1 */\n' + ' )' % (tp.name, tp.name)) + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, s) + + def _emit_bytecode_RawFunctionType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_FUNCTION, self._typesdict[tp.result]) + index += 1 + for tp1 in tp.args: + realindex = self._typesdict[tp1] + if index != realindex: + if isinstance(tp1, model.PrimitiveType): + self._emit_bytecode_PrimitiveType(tp1, index) + else: + self.cffi_types[index] = CffiOp(OP_NOOP, realindex) + index += 1 + flags = int(tp.ellipsis) + if tp.abi is not None: + if tp.abi == '__stdcall': + flags |= 2 + else: + raise NotImplementedError("abi=%r" % (tp.abi,)) + self.cffi_types[index] = CffiOp(OP_FUNCTION_END, flags) + + def _emit_bytecode_PointerType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[tp.totype]) + + _emit_bytecode_ConstPointerType = _emit_bytecode_PointerType + _emit_bytecode_NamedPointerType = _emit_bytecode_PointerType + + def _emit_bytecode_FunctionPtrType(self, tp, index): + raw = tp.as_raw_function() + self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[raw]) + + def _emit_bytecode_ArrayType(self, tp, index): + item_index = self._typesdict[tp.item] + if tp.length is None: + self.cffi_types[index] = CffiOp(OP_OPEN_ARRAY, item_index) + elif tp.length == '...': + raise VerificationError( + "type %s badly placed: the '...' array length can only be " + "used on global arrays or on fields of structures" % ( + str(tp).replace('/*...*/', '...'),)) + else: + assert self.cffi_types[index + 1] == 'LEN' + self.cffi_types[index] = CffiOp(OP_ARRAY, item_index) + self.cffi_types[index + 1] = CffiOp(None, str(tp.length)) + + def _emit_bytecode_StructType(self, tp, index): + struct_index = self._struct_unions[tp] + self.cffi_types[index] = CffiOp(OP_STRUCT_UNION, struct_index) + _emit_bytecode_UnionType = _emit_bytecode_StructType + + def _emit_bytecode_EnumType(self, tp, index): + enum_index = self._enums[tp] + self.cffi_types[index] = CffiOp(OP_ENUM, enum_index) + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super(NativeIO, self).write(s) + +def _make_c_or_py_source(ffi, module_name, preamble, target_file, verbose): + if verbose: + print("generating %s" % (target_file,)) + recompiler = Recompiler(ffi, module_name, + target_is_python=(preamble is None)) + recompiler.collect_type_table() + recompiler.collect_step_tables() + f = NativeIO() + recompiler.write_source_to_f(f, preamble) + output = f.getvalue() + try: + with open(target_file, 'r') as f1: + if f1.read(len(output) + 1) != output: + raise IOError + if verbose: + print("(already up-to-date)") + return False # already up-to-date + except IOError: + tmp_file = '%s.~%d' % (target_file, os.getpid()) + with open(tmp_file, 'w') as f1: + f1.write(output) + try: + os.rename(tmp_file, target_file) + except OSError: + os.unlink(target_file) + os.rename(tmp_file, target_file) + return True + +def make_c_source(ffi, module_name, preamble, target_c_file, verbose=False): + assert preamble is not None + return _make_c_or_py_source(ffi, module_name, preamble, target_c_file, + verbose) + +def make_py_source(ffi, module_name, target_py_file, verbose=False): + return _make_c_or_py_source(ffi, module_name, None, target_py_file, + verbose) + +def _modname_to_file(outputdir, modname, extension): + parts = modname.split('.') + try: + os.makedirs(os.path.join(outputdir, *parts[:-1])) + except OSError: + pass + parts[-1] += extension + return os.path.join(outputdir, *parts), parts + + +# Aaargh. Distutils is not tested at all for the purpose of compiling +# DLLs that are not extension modules. Here are some hacks to work +# around that, in the _patch_for_*() functions... + +def _patch_meth(patchlist, cls, name, new_meth): + old = getattr(cls, name) + patchlist.append((cls, name, old)) + setattr(cls, name, new_meth) + return old + +def _unpatch_meths(patchlist): + for cls, name, old_meth in reversed(patchlist): + setattr(cls, name, old_meth) + +def _patch_for_embedding(patchlist): + if sys.platform == 'win32': + # we must not remove the manifest when building for embedding! + from distutils.msvc9compiler import MSVCCompiler + _patch_meth(patchlist, MSVCCompiler, '_remove_visual_c_ref', + lambda self, manifest_file: manifest_file) + + if sys.platform == 'darwin': + # we must not make a '-bundle', but a '-dynamiclib' instead + from distutils.ccompiler import CCompiler + def my_link_shared_object(self, *args, **kwds): + if '-bundle' in self.linker_so: + self.linker_so = list(self.linker_so) + i = self.linker_so.index('-bundle') + self.linker_so[i] = '-dynamiclib' + return old_link_shared_object(self, *args, **kwds) + old_link_shared_object = _patch_meth(patchlist, CCompiler, + 'link_shared_object', + my_link_shared_object) + +def _patch_for_target(patchlist, target): + from distutils.command.build_ext import build_ext + # if 'target' is different from '*', we need to patch some internal + # method to just return this 'target' value, instead of having it + # built from module_name + if target.endswith('.*'): + target = target[:-2] + if sys.platform == 'win32': + target += '.dll' + elif sys.platform == 'darwin': + target += '.dylib' + else: + target += '.so' + _patch_meth(patchlist, build_ext, 'get_ext_filename', + lambda self, ext_name: target) + + +def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True, + c_file=None, source_extension='.c', extradir=None, + compiler_verbose=1, target=None, debug=None, **kwds): + if not isinstance(module_name, str): + module_name = module_name.encode('ascii') + if ffi._windows_unicode: + ffi._apply_windows_unicode(kwds) + if preamble is not None: + embedding = (ffi._embedding is not None) + if embedding: + ffi._apply_embedding_fix(kwds) + if c_file is None: + c_file, parts = _modname_to_file(tmpdir, module_name, + source_extension) + if extradir: + parts = [extradir] + parts + ext_c_file = os.path.join(*parts) + else: + ext_c_file = c_file + # + if target is None: + if embedding: + target = '%s.*' % module_name + else: + target = '*' + # + ext = ffiplatform.get_extension(ext_c_file, module_name, **kwds) + updated = make_c_source(ffi, module_name, preamble, c_file, + verbose=compiler_verbose) + if call_c_compiler: + patchlist = [] + cwd = os.getcwd() + try: + if embedding: + _patch_for_embedding(patchlist) + if target != '*': + _patch_for_target(patchlist, target) + if compiler_verbose: + if tmpdir == '.': + msg = 'the current directory is' + else: + msg = 'setting the current directory to' + print('%s %r' % (msg, os.path.abspath(tmpdir))) + os.chdir(tmpdir) + outputfilename = ffiplatform.compile('.', ext, + compiler_verbose, debug) + finally: + os.chdir(cwd) + _unpatch_meths(patchlist) + return outputfilename + else: + return ext, updated + else: + if c_file is None: + c_file, _ = _modname_to_file(tmpdir, module_name, '.py') + updated = make_py_source(ffi, module_name, c_file, + verbose=compiler_verbose) + if call_c_compiler: + return c_file + else: + return None, updated + diff --git a/cffi/setuptools_ext.py b/cffi/setuptools_ext.py new file mode 100644 index 0000000..df5a518 --- /dev/null +++ b/cffi/setuptools_ext.py @@ -0,0 +1,217 @@ +import os +import sys + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +def error(msg): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError(msg) + + +def execfile(filename, glob): + # We use execfile() (here rewritten for Python 3) instead of + # __import__() to load the build script. The problem with + # a normal import is that in some packages, the intermediate + # __init__.py files may already try to import the file that + # we are generating. + with open(filename) as f: + src = f.read() + src += '\n' # Python 2.6 compatibility + code = compile(src, filename, 'exec') + exec(code, glob, glob) + + +def add_cffi_module(dist, mod_spec): + from cffi.api import FFI + + if not isinstance(mod_spec, basestring): + error("argument to 'cffi_modules=...' must be a str or a list of str," + " not %r" % (type(mod_spec).__name__,)) + mod_spec = str(mod_spec) + try: + build_file_name, ffi_var_name = mod_spec.split(':') + except ValueError: + error("%r must be of the form 'path/build.py:ffi_variable'" % + (mod_spec,)) + if not os.path.exists(build_file_name): + ext = '' + rewritten = build_file_name.replace('.', '/') + '.py' + if os.path.exists(rewritten): + ext = ' (rewrite cffi_modules to [%r])' % ( + rewritten + ':' + ffi_var_name,) + error("%r does not name an existing file%s" % (build_file_name, ext)) + + mod_vars = {'__name__': '__cffi__', '__file__': build_file_name} + execfile(build_file_name, mod_vars) + + try: + ffi = mod_vars[ffi_var_name] + except KeyError: + error("%r: object %r not found in module" % (mod_spec, + ffi_var_name)) + if not isinstance(ffi, FFI): + ffi = ffi() # maybe it's a function instead of directly an ffi + if not isinstance(ffi, FFI): + error("%r is not an FFI instance (got %r)" % (mod_spec, + type(ffi).__name__)) + if not hasattr(ffi, '_assigned_source'): + error("%r: the set_source() method was not called" % (mod_spec,)) + module_name, source, source_extension, kwds = ffi._assigned_source + if ffi._windows_unicode: + kwds = kwds.copy() + ffi._apply_windows_unicode(kwds) + + if source is None: + _add_py_module(dist, ffi, module_name) + else: + _add_c_module(dist, ffi, module_name, source, source_extension, kwds) + +def _set_py_limited_api(Extension, kwds): + """ + Add py_limited_api to kwds if setuptools >= 26 is in use. + Do not alter the setting if it already exists. + Setuptools takes care of ignoring the flag on Python 2 and PyPy. + + CPython itself should ignore the flag in a debugging version + (by not listing .abi3.so in the extensions it supports), but + it doesn't so far, creating troubles. That's why we check + for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent + of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401) + + On Windows, with CPython <= 3.4, it's better not to use py_limited_api + because virtualenv *still* doesn't copy PYTHON3.DLL on these versions. + For now we'll skip py_limited_api on all Windows versions to avoid an + inconsistent mess. + """ + if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount') + and sys.platform != 'win32'): + import setuptools + try: + setuptools_major_version = int(setuptools.__version__.partition('.')[0]) + if setuptools_major_version >= 26: + kwds['py_limited_api'] = True + except ValueError: # certain development versions of setuptools + # If we don't know the version number of setuptools, we + # try to set 'py_limited_api' anyway. At worst, we get a + # warning. + kwds['py_limited_api'] = True + return kwds + +def _add_c_module(dist, ffi, module_name, source, source_extension, kwds): + from distutils.core import Extension + # We are a setuptools extension. Need this build_ext for py_limited_api. + from setuptools.command.build_ext import build_ext + from distutils.dir_util import mkpath + from distutils import log + from cffi import recompiler + + allsources = ['$PLACEHOLDER'] + allsources.extend(kwds.pop('sources', [])) + kwds = _set_py_limited_api(Extension, kwds) + ext = Extension(name=module_name, sources=allsources, **kwds) + + def make_mod(tmpdir, pre_run=None): + c_file = os.path.join(tmpdir, module_name + source_extension) + log.info("generating cffi module %r" % c_file) + mkpath(tmpdir) + # a setuptools-only, API-only hook: called with the "ext" and "ffi" + # arguments just before we turn the ffi into C code. To use it, + # subclass the 'distutils.command.build_ext.build_ext' class and + # add a method 'def pre_run(self, ext, ffi)'. + if pre_run is not None: + pre_run(ext, ffi) + updated = recompiler.make_c_source(ffi, module_name, source, c_file) + if not updated: + log.info("already up-to-date") + return c_file + + if dist.ext_modules is None: + dist.ext_modules = [] + dist.ext_modules.append(ext) + + base_class = dist.cmdclass.get('build_ext', build_ext) + class build_ext_make_mod(base_class): + def run(self): + if ext.sources[0] == '$PLACEHOLDER': + pre_run = getattr(self, 'pre_run', None) + ext.sources[0] = make_mod(self.build_temp, pre_run) + base_class.run(self) + dist.cmdclass['build_ext'] = build_ext_make_mod + # NB. multiple runs here will create multiple 'build_ext_make_mod' + # classes. Even in this case the 'build_ext' command should be + # run once; but just in case, the logic above does nothing if + # called again. + + +def _add_py_module(dist, ffi, module_name): + from distutils.dir_util import mkpath + from setuptools.command.build_py import build_py + from setuptools.command.build_ext import build_ext + from distutils import log + from cffi import recompiler + + def generate_mod(py_file): + log.info("generating cffi module %r" % py_file) + mkpath(os.path.dirname(py_file)) + updated = recompiler.make_py_source(ffi, module_name, py_file) + if not updated: + log.info("already up-to-date") + + base_class = dist.cmdclass.get('build_py', build_py) + class build_py_make_mod(base_class): + def run(self): + base_class.run(self) + module_path = module_name.split('.') + module_path[-1] += '.py' + generate_mod(os.path.join(self.build_lib, *module_path)) + def get_source_files(self): + # This is called from 'setup.py sdist' only. Exclude + # the generate .py module in this case. + saved_py_modules = self.py_modules + try: + if saved_py_modules: + self.py_modules = [m for m in saved_py_modules + if m != module_name] + return base_class.get_source_files(self) + finally: + self.py_modules = saved_py_modules + dist.cmdclass['build_py'] = build_py_make_mod + + # distutils and setuptools have no notion I could find of a + # generated python module. If we don't add module_name to + # dist.py_modules, then things mostly work but there are some + # combination of options (--root and --record) that will miss + # the module. So we add it here, which gives a few apparently + # harmless warnings about not finding the file outside the + # build directory. + # Then we need to hack more in get_source_files(); see above. + if dist.py_modules is None: + dist.py_modules = [] + dist.py_modules.append(module_name) + + # the following is only for "build_ext -i" + base_class_2 = dist.cmdclass.get('build_ext', build_ext) + class build_ext_make_mod(base_class_2): + def run(self): + base_class_2.run(self) + if self.inplace: + # from get_ext_fullpath() in distutils/command/build_ext.py + module_path = module_name.split('.') + package = '.'.join(module_path[:-1]) + build_py = self.get_finalized_command('build_py') + package_dir = build_py.get_package_dir(package) + file_name = module_path[-1] + '.py' + generate_mod(os.path.join(package_dir, file_name)) + dist.cmdclass['build_ext'] = build_ext_make_mod + +def cffi_modules(dist, attr, value): + assert attr == 'cffi_modules' + if isinstance(value, basestring): + value = [value] + + for cffi_module in value: + add_cffi_module(dist, cffi_module) diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py new file mode 100644 index 0000000..536f11f --- /dev/null +++ b/cffi/vengine_cpy.py @@ -0,0 +1,1015 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, imp +from . import model +from .error import VerificationError + + +class VCPythonEngine(object): + _class_key = 'x' + _gen_python_module = True + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self._struct_pending_verification = {} + self._types_of_builtin_functions = {} + + def patch_extension_kwds(self, kwds): + pass + + def find_module(self, module_name, path, so_suffixes): + try: + f, filename, descr = imp.find_module(module_name, path) + except ImportError: + return None + if f is not None: + f.close() + # Note that after a setuptools installation, there are both .py + # and .so files with the same basename. The code here relies on + # imp.find_module() locating the .so in priority. + if descr[0] not in so_suffixes: + return None + return filename + + def collect_types(self): + self._typesdict = {} + self._generate("collecttype") + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _do_collect_type(self, tp): + if ((not isinstance(tp, model.PrimitiveType) + or tp.name == 'long double') + and tp not in self._typesdict): + num = len(self._typesdict) + self._typesdict[tp] = num + + def write_source_to_f(self): + self.collect_types() + # + # The new module will have a _cffi_setup() function that receives + # objects from the ffi world, and that calls some setup code in + # the module. This setup code is split in several independent + # functions, e.g. one per constant. The functions are "chained" + # by ending in a tail call to each other. + # + # This is further split in two chained lists, depending on if we + # can do it at import-time or if we must wait for _cffi_setup() to + # provide us with the objects. This is needed because we + # need the values of the enum constants in order to build the + # that we may have to pass to _cffi_setup(). + # + # The following two 'chained_list_constants' items contains + # the head of these two chained lists, as a string that gives the + # call to do, if any. + self._chained_list_constants = ['((void)lib,0)', '((void)lib,0)'] + # + prnt = self._prnt + # first paste some standard set of lines that are mostly '#define' + prnt(cffimod_header) + prnt() + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate("decl") + # + # implement the function _cffi_setup_custom() as calling the + # head of the chained list. + self._generate_setup_custom() + prnt() + # + # produce the method table, including the entries for the + # generated Python->C function wrappers, which are done + # by generate_cpy_function_method(). + prnt('static PyMethodDef _cffi_methods[] = {') + self._generate("method") + prnt(' {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL},') + prnt(' {NULL, NULL, 0, NULL} /* Sentinel */') + prnt('};') + prnt() + # + # standard init. + modname = self.verifier.get_module_name() + constants = self._chained_list_constants[False] + prnt('#if PY_MAJOR_VERSION >= 3') + prnt() + prnt('static struct PyModuleDef _cffi_module_def = {') + prnt(' PyModuleDef_HEAD_INIT,') + prnt(' "%s",' % modname) + prnt(' NULL,') + prnt(' -1,') + prnt(' _cffi_methods,') + prnt(' NULL, NULL, NULL, NULL') + prnt('};') + prnt() + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = PyModule_Create(&_cffi_module_def);') + prnt(' if (lib == NULL)') + prnt(' return NULL;') + prnt(' if (%s < 0 || _cffi_init() < 0) {' % (constants,)) + prnt(' Py_DECREF(lib);') + prnt(' return NULL;') + prnt(' }') + prnt(' return lib;') + prnt('}') + prnt() + prnt('#else') + prnt() + prnt('PyMODINIT_FUNC') + prnt('init%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = Py_InitModule("%s", _cffi_methods);' % modname) + prnt(' if (lib == NULL)') + prnt(' return;') + prnt(' if (%s < 0 || _cffi_init() < 0)' % (constants,)) + prnt(' return;') + prnt(' return;') + prnt('}') + prnt() + prnt('#endif') + + def load_library(self, flags=None): + # XXX review all usages of 'self' here! + # import it as a new extension module + imp.acquire_lock() + try: + if hasattr(sys, "getdlopenflags"): + previous_flags = sys.getdlopenflags() + try: + if hasattr(sys, "setdlopenflags") and flags is not None: + sys.setdlopenflags(flags) + module = imp.load_dynamic(self.verifier.get_module_name(), + self.verifier.modulefilename) + except ImportError as e: + error = "importing %r: %s" % (self.verifier.modulefilename, e) + raise VerificationError(error) + finally: + if hasattr(sys, "setdlopenflags"): + sys.setdlopenflags(previous_flags) + finally: + imp.release_lock() + # + # call loading_cpy_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + # + # the C code will need the objects. Collect them in + # order in a list. + revmapping = dict([(value, key) + for (key, value) in self._typesdict.items()]) + lst = [revmapping[i] for i in range(len(revmapping))] + lst = list(map(self.ffi._get_cached_btype, lst)) + # + # build the FFILibrary class and instance and call _cffi_setup(). + # this will set up some fields like '_cffi_types', and only then + # it will invoke the chained list of functions that will really + # build (notably) the constant objects, as if they are + # pointers, and store them as attributes on the 'library' object. + class FFILibrary(object): + _cffi_python_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + list(self.__dict__) + library = FFILibrary() + if module._cffi_setup(lst, VerificationError, library): + import warnings + warnings.warn("reimporting %r might overwrite older definitions" + % (self.verifier.get_module_name())) + # + # finally, call the loaded_cpy_xxx() functions. This will perform + # the final adjustments, like copying the Python->C wrapper + # functions from the module to the 'library' object, and setting + # up the FFILibrary class with properties for the global C variables. + self._load(module, 'loaded', library=library) + module._cffi_original_ffi = self.ffi + module._cffi_types_of_builtin_funcs = self._types_of_builtin_functions + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_cpy_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + else: + converter = '(%s)_cffi_to_c_%s' % (tp.get_c_name(''), + tp.name.replace(' ', '_')) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif isinstance(tp, (model.StructOrUnion, model.EnumType)): + # a struct (not a struct pointer) as a function argument + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' if (datasize < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' %s = alloca((size_t)datasize);' % (tovar,)) + self._prnt(' memset((void *)%s, 0, (size_t)datasize);' % (tovar,)) + self._prnt(' if (_cffi_convert_array_from_object(' + '(char *)%s, _cffi_type(%d), %s) < 0)' % ( + tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif tp.name != 'long double': + return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs: generates no code so far + + _generate_cpy_typedef_collecttype = _generate_nothing + _generate_cpy_typedef_decl = _generate_nothing + _generate_cpy_typedef_method = _generate_nothing + _loading_cpy_typedef = _loaded_noop + _loaded_cpy_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + self._do_collect_type(tp) + else: + # don't call _do_collect_type(tp) in this common case, + # otherwise test_autofilled_struct_as_argument fails + for type in tp.args: + self._do_collect_type(type) + self._do_collect_type(tp.result) + + def _generate_cpy_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + prnt(' %s;' % type.get_c_name(' x%d' % i, context)) + # + localvars = set() + for type in tp.args: + self._extra_local_variables(type, localvars) + for decl in localvars: + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + prnt(' %s;' % tp.result.get_c_name(' result', context)) + else: + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( + 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + prnt(' { %s%s(%s); }' % ( + result_code, name, + ', '.join(['x%d' % i for i in range(len(tp.args))]))) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' return %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + else: + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + prnt() + + def _generate_cpy_function_method(self, tp, name): + if tp.ellipsis: + return + numargs = len(tp.args) + if numargs == 0: + meth = 'METH_NOARGS' + elif numargs == 1: + meth = 'METH_O' + else: + meth = 'METH_VARARGS' + self._prnt(' {"%s", _cffi_f_%s, %s, NULL},' % (name, name, meth)) + + _loading_cpy_function = _loaded_noop + + def _loaded_cpy_function(self, tp, name, module, library): + if tp.ellipsis: + return + func = getattr(module, name) + setattr(library, name, func) + self._types_of_builtin_functions[func] = tp + + # ---------- + # named structs + + _generate_cpy_struct_collecttype = _generate_nothing + def _generate_cpy_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + def _generate_cpy_struct_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'struct', name) + def _loading_cpy_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + def _loaded_cpy_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + _generate_cpy_union_collecttype = _generate_nothing + def _generate_cpy_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + def _generate_cpy_union_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'union', name) + def _loading_cpy_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + def _loaded_cpy_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('static PyObject *') + prnt('%s(PyObject *self, PyObject *noarg)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static Py_ssize_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' (void)self; /* unused */') + prnt(' (void)noarg; /* unused */') + prnt(' return _cffi_get_struct_layout(nums);') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _generate_struct_or_union_method(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + self._prnt(' {"%s", %s, METH_NOARGS, NULL},' % (layoutfuncname, + layoutfuncname)) + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + function = getattr(module, layoutfuncname) + layout = function() + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + _generate_cpy_anonymous_collecttype = _generate_nothing + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _generate_cpy_anonymous_method(self, tp, name): + if not isinstance(tp, model.EnumType): + self._generate_struct_or_union_method(tp, '', name) + + def _loading_cpy_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_cpy_enum(tp, name, module) + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_cpy_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_cpy_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + vartp=None, delayed=True, size_too=False, + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + prnt(' PyObject *o;') + prnt(' int res;') + if not is_int: + prnt(' %s;' % (vartp or tp).get_c_name(' i', name)) + else: + assert category == 'const' + # + if check_value is not None: + self._check_int_constant_value(name, check_value) + # + if not is_int: + if category == 'var': + realexpr = '&' + name + else: + realexpr = name + prnt(' i = (%s);' % (realexpr,)) + prnt(' o = %s;' % (self._convert_expr_from_c(tp, 'i', + 'variable type'),)) + assert delayed + else: + prnt(' o = _cffi_from_c_int_const(%s);' % name) + prnt(' if (o == NULL)') + prnt(' return -1;') + if size_too: + prnt(' {') + prnt(' PyObject *o1 = o;') + prnt(' o = Py_BuildValue("On", o1, (Py_ssize_t)sizeof(%s));' + % (name,)) + prnt(' Py_DECREF(o1);') + prnt(' if (o == NULL)') + prnt(' return -1;') + prnt(' }') + prnt(' res = PyObject_SetAttrString(lib, "%s", o);' % name) + prnt(' Py_DECREF(o);') + prnt(' if (res < 0)') + prnt(' return -1;') + prnt(' return %s;' % self._chained_list_constants[delayed]) + self._chained_list_constants[delayed] = funcname + '(lib)' + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + if not is_int: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + _generate_cpy_constant_method = _generate_nothing + _loading_cpy_constant = _loaded_noop + _loaded_cpy_constant = _loaded_noop + + # ---------- + # enums + + def _check_int_constant_value(self, name, value, err_prefix=''): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' snprintf(buf, 63, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' snprintf(buf, 63, "%%lu", (unsigned long)(%s));' % + name) + prnt(' PyErr_Format(_cffi_VerificationError,') + prnt(' "%s%s has the real value %s, not %s",') + prnt(' "%s", "%s", buf, "%d");' % ( + err_prefix, name, value)) + prnt(' return -1;') + prnt(' }') + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_cpy_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator, delayed=False) + return + # + funcname = self._enum_funcname(prefix, name) + prnt = self._prnt + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue, + "enum %s: " % name) + prnt(' return %s;' % self._chained_list_constants[True]) + self._chained_list_constants[True] = funcname + '(lib)' + prnt('}') + prnt() + + _generate_cpy_enum_collecttype = _generate_nothing + _generate_cpy_enum_method = _generate_nothing + + def _loading_cpy_enum(self, tp, name, module): + if tp.partial: + enumvalues = [getattr(module, enumerator) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + + def _loaded_cpy_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + _generate_cpy_macro_collecttype = _generate_nothing + _generate_cpy_macro_method = _generate_nothing + _loading_cpy_macro = _loaded_noop + _loaded_cpy_macro = _loaded_noop + + # ---------- + # global variables + + def _generate_cpy_variable_collecttype(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + else: + tp_ptr = model.PointerType(tp) + self._do_collect_type(tp_ptr) + + def _generate_cpy_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + self._generate_cpy_const(False, name, tp, vartp=tp_ptr, + size_too = (tp.length == '...')) + else: + tp_ptr = model.PointerType(tp) + self._generate_cpy_const(False, name, tp_ptr, category='var') + + _generate_cpy_variable_method = _generate_nothing + _loading_cpy_variable = _loaded_noop + + def _loaded_cpy_variable(self, tp, name, module, library): + value = getattr(library, name) + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length == '...': + assert isinstance(value, tuple) + (value, size) = value + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + ptr = value + delattr(library, name) + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + + # ---------- + + def _generate_setup_custom(self): + prnt = self._prnt + prnt('static int _cffi_setup_custom(PyObject *lib)') + prnt('{') + prnt(' return %s;' % self._chained_list_constants[True]) + prnt('}') + +cffimod_header = r''' +#include +#include + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +#endif + +#if PY_MAJOR_VERSION < 3 +# undef PyCapsule_CheckExact +# undef PyCapsule_GetPointer +# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) +# define PyCapsule_GetPointer(capsule, name) \ + (PyCObject_AsVoidPtr(capsule)) +#endif + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int_const(x) \ + (((x) > 0) ? \ + ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ + ((long long)(x) >= (long long)LONG_MIN) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromLongLong((long long)(x))) + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) +#define _CFFI_NUM_EXPORTS 25 + +typedef struct _ctypedescr CTypeDescrObject; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; +static PyObject *_cffi_types, *_cffi_VerificationError; + +static int _cffi_setup_custom(PyObject *lib); /* forward */ + +static PyObject *_cffi_setup(PyObject *self, PyObject *args) +{ + PyObject *library; + int was_alive = (_cffi_types != NULL); + (void)self; /* unused */ + if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, + &library)) + return NULL; + Py_INCREF(_cffi_types); + Py_INCREF(_cffi_VerificationError); + if (_cffi_setup_custom(library) < 0) + return NULL; + return PyBool_FromLong(was_alive); +} + +static int _cffi_init(void) +{ + PyObject *module, *c_api_object = NULL; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) + goto failure; + if (!PyCapsule_CheckExact(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + goto failure; + } + memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), + _CFFI_NUM_EXPORTS * sizeof(void *)); + + Py_DECREF(module); + Py_DECREF(c_api_object); + return 0; + + failure: + Py_XDECREF(module); + Py_XDECREF(c_api_object); + return -1; +} + +#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) + +/**********/ +''' diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py new file mode 100644 index 0000000..a64ff64 --- /dev/null +++ b/cffi/vengine_gen.py @@ -0,0 +1,675 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os +import types + +from . import model +from .error import VerificationError + + +class VGenericEngine(object): + _class_key = 'g' + _gen_python_module = False + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self.export_symbols = [] + self._struct_pending_verification = {} + + def patch_extension_kwds(self, kwds): + # add 'export_symbols' to the dictionary. Note that we add the + # list before filling it. When we fill it, it will thus also show + # up in kwds['export_symbols']. + kwds.setdefault('export_symbols', self.export_symbols) + + def find_module(self, module_name, path, so_suffixes): + for so_suffix in so_suffixes: + basename = module_name + so_suffix + if path is None: + path = sys.path + for dirname in path: + filename = os.path.join(dirname, basename) + if os.path.isfile(filename): + return filename + + def collect_types(self): + pass # not needed in the generic engine + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def write_source_to_f(self): + prnt = self._prnt + # first paste some standard set of lines that are mostly '#include' + prnt(cffimod_header) + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + # + # call generate_gen_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate('decl') + # + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + if sys.platform == 'win32': + if sys.version_info >= (3,): + prefix = 'PyInit_' + else: + prefix = 'init' + modname = self.verifier.get_module_name() + prnt("void %s%s(void) { }\n" % (prefix, modname)) + + def load_library(self, flags=0): + # import it with the CFFI backend + backend = self.ffi._backend + # needs to make a path that contains '/', on Posix + filename = os.path.join(os.curdir, self.verifier.modulefilename) + module = backend.load_library(filename, flags) + # + # call loading_gen_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + + # build the FFILibrary class and instance, this is a module subclass + # because modules are expected to have usually-constant-attributes and + # in PyPy this means the JIT is able to treat attributes as constant, + # which we want. + class FFILibrary(types.ModuleType): + _cffi_generic_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + library = FFILibrary("") + # + # finally, call the loaded_gen_xxx() functions. This will set + # up the 'library' object. + self._load(module, 'loaded', library=library) + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_gen_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_gen_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + # typedefs: generates no code so far + + _generate_gen_typedef_decl = _generate_nothing + _loading_gen_typedef = _loaded_noop + _loaded_gen_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_gen_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no _cffi_f_%s wrapper) + self._generate_gen_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + argnames = [] + for i, type in enumerate(tp.args): + indirection = '' + if isinstance(type, model.StructOrUnion): + indirection = '*' + argnames.append('%sx%d' % (indirection, i)) + context = 'argument of %s' % name + arglist = [type.get_c_name(' %s' % arg, context) + for type, arg in zip(tp.args, argnames)] + tpresult = tp.result + if isinstance(tpresult, model.StructOrUnion): + arglist.insert(0, tpresult.get_c_name(' *r', context)) + tpresult = model.void_type + arglist = ', '.join(arglist) or 'void' + wrappername = '_cffi_f_%s' % name + self.export_symbols.append(wrappername) + if tp.abi: + abi = tp.abi + ' ' + else: + abi = '' + funcdecl = ' %s%s(%s)' % (abi, wrappername, arglist) + context = 'result of %s' % name + prnt(tpresult.get_c_name(funcdecl, context)) + prnt('{') + # + if isinstance(tp.result, model.StructOrUnion): + result_code = '*r = ' + elif not isinstance(tp.result, model.VoidType): + result_code = 'return ' + else: + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, ', '.join(argnames))) + prnt('}') + prnt() + + _loading_gen_function = _loaded_noop + + def _loaded_gen_function(self, tp, name, module, library): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + newfunction = self._load_constant(False, tp, name, module) + else: + indirections = [] + base_tp = tp + if (any(isinstance(typ, model.StructOrUnion) for typ in tp.args) + or isinstance(tp.result, model.StructOrUnion)): + indirect_args = [] + for i, typ in enumerate(tp.args): + if isinstance(typ, model.StructOrUnion): + typ = model.PointerType(typ) + indirections.append((i, typ)) + indirect_args.append(typ) + indirect_result = tp.result + if isinstance(indirect_result, model.StructOrUnion): + if indirect_result.fldtypes is None: + raise TypeError("'%s' is used as result type, " + "but is opaque" % ( + indirect_result._get_c_name(),)) + indirect_result = model.PointerType(indirect_result) + indirect_args.insert(0, indirect_result) + indirections.insert(0, ("result", indirect_result)) + indirect_result = model.void_type + tp = model.FunctionPtrType(tuple(indirect_args), + indirect_result, tp.ellipsis) + BFunc = self.ffi._get_cached_btype(tp) + wrappername = '_cffi_f_%s' % name + newfunction = module.load_function(BFunc, wrappername) + for i, typ in indirections: + newfunction = self._make_struct_wrapper(newfunction, i, typ, + base_tp) + setattr(library, name, newfunction) + type(library)._cffi_dir.append(name) + + def _make_struct_wrapper(self, oldfunc, i, tp, base_tp): + backend = self.ffi._backend + BType = self.ffi._get_cached_btype(tp) + if i == "result": + ffi = self.ffi + def newfunc(*args): + res = ffi.new(BType) + oldfunc(res, *args) + return res[0] + else: + def newfunc(*args): + args = args[:i] + (backend.newp(BType, args[i]),) + args[i+1:] + return oldfunc(*args) + newfunc._cffi_base_type = base_tp + return newfunc + + # ---------- + # named structs + + def _generate_gen_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + + def _loading_gen_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + + def _loaded_gen_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_gen_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + + def _loading_gen_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + + def _loaded_gen_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + self.export_symbols.append(layoutfuncname) + prnt('intptr_t %s(intptr_t i)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static intptr_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' return nums[i];') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + BFunc = self.ffi._typeof_locked("intptr_t(*)(intptr_t)")[0] + function = module.load_function(BFunc, layoutfuncname) + layout = [] + num = 0 + while True: + x = function(num) + if x < 0: break + layout.append(x) + num += 1 + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_gen_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_gen_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _loading_gen_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_gen_enum(tp, name, module, '') + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_gen_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_gen_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_gen_const(self, is_int, name, tp=None, category='const', + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + self.export_symbols.append(funcname) + if check_value is not None: + assert is_int + assert category == 'const' + prnt('int %s(char *out_error)' % funcname) + prnt('{') + self._check_int_constant_value(name, check_value) + prnt(' return 0;') + prnt('}') + elif is_int: + assert category == 'const' + prnt('int %s(long long *out_value)' % funcname) + prnt('{') + prnt(' *out_value = (long long)(%s);' % (name,)) + prnt(' return (%s) <= 0;' % (name,)) + prnt('}') + else: + assert tp is not None + assert check_value is None + if category == 'var': + ampersand = '&' + else: + ampersand = '' + extra = '' + if category == 'const' and isinstance(tp, model.StructOrUnion): + extra = 'const *' + ampersand = '&' + prnt(tp.get_c_name(' %s%s(void)' % (extra, funcname), name)) + prnt('{') + prnt(' return (%s%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_gen_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_gen_const(is_int, name, tp) + + _loading_gen_constant = _loaded_noop + + def _load_constant(self, is_int, tp, name, module, check_value=None): + funcname = '_cffi_const_%s' % name + if check_value is not None: + assert is_int + self._load_known_int_constant(module, funcname) + value = check_value + elif is_int: + BType = self.ffi._typeof_locked("long long*")[0] + BFunc = self.ffi._typeof_locked("int(*)(long long*)")[0] + function = module.load_function(BFunc, funcname) + p = self.ffi.new(BType) + negative = function(p) + value = int(p[0]) + if value < 0 and not negative: + BLongLong = self.ffi._typeof_locked("long long")[0] + value += (1 << (8*self.ffi.sizeof(BLongLong))) + else: + assert check_value is None + fntypeextra = '(*)(void)' + if isinstance(tp, model.StructOrUnion): + fntypeextra = '*' + fntypeextra + BFunc = self.ffi._typeof_locked(tp.get_c_name(fntypeextra, name))[0] + function = module.load_function(BFunc, funcname) + value = function() + if isinstance(tp, model.StructOrUnion): + value = value[0] + return value + + def _loaded_gen_constant(self, tp, name, module, library): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + value = self._load_constant(is_int, tp, name, module) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + + # ---------- + # enums + + def _check_int_constant_value(self, name, value): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' sprintf(buf, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' sprintf(buf, "%%lu", (unsigned long)(%s));' % + name) + prnt(' sprintf(out_error, "%s has the real value %s, not %s",') + prnt(' "%s", buf, "%d");' % (name[:100], value)) + prnt(' return -1;') + prnt(' }') + + def _load_known_int_constant(self, module, funcname): + BType = self.ffi._typeof_locked("char[]")[0] + BFunc = self.ffi._typeof_locked("int(*)(char*)")[0] + function = module.load_function(BFunc, funcname) + p = self.ffi.new(BType, 256) + if function(p) < 0: + error = self.ffi.string(p) + if sys.version_info >= (3,): + error = str(error, 'utf-8') + raise VerificationError(error) + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_gen_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_gen_const(True, enumerator) + return + # + funcname = self._enum_funcname(prefix, name) + self.export_symbols.append(funcname) + prnt = self._prnt + prnt('int %s(char *out_error)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue) + prnt(' return 0;') + prnt('}') + prnt() + + def _loading_gen_enum(self, tp, name, module, prefix='enum'): + if tp.partial: + enumvalues = [self._load_constant(True, tp, enumerator, module) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + else: + funcname = self._enum_funcname(prefix, name) + self._load_known_int_constant(module, funcname) + + def _loaded_gen_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + type(library)._cffi_dir.append(enumerator) + + # ---------- + # macros: for now only for integers + + def _generate_gen_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_gen_const(True, name, check_value=check_value) + + _loading_gen_macro = _loaded_noop + + def _loaded_gen_macro(self, tp, name, module, library): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + value = self._load_constant(True, tp, name, module, + check_value=check_value) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + + # ---------- + # global variables + + def _generate_gen_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + if tp.length == '...': + prnt = self._prnt + funcname = '_cffi_sizeof_%s' % (name,) + self.export_symbols.append(funcname) + prnt("size_t %s(void)" % funcname) + prnt("{") + prnt(" return sizeof(%s);" % (name,)) + prnt("}") + tp_ptr = model.PointerType(tp.item) + self._generate_gen_const(False, name, tp_ptr) + else: + tp_ptr = model.PointerType(tp) + self._generate_gen_const(False, name, tp_ptr, category='var') + + _loading_gen_variable = _loaded_noop + + def _loaded_gen_variable(self, tp, name, module, library): + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length == '...': + funcname = '_cffi_sizeof_%s' % (name,) + BFunc = self.ffi._typeof_locked('size_t(*)(void)')[0] + function = module.load_function(BFunc, funcname) + size = function() + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + tp_ptr = model.PointerType(tp.item) + value = self._load_constant(False, tp_ptr, name, module) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + funcname = '_cffi_var_%s' % name + BFunc = self.ffi._typeof_locked(tp.get_c_name('*(*)(void)', name))[0] + function = module.load_function(BFunc, funcname) + ptr = function() + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + +cffimod_header = r''' +#include +#include +#include +#include +#include /* XXX for ssize_t on some platforms */ + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +#endif +''' diff --git a/cffi/verifier.py b/cffi/verifier.py new file mode 100644 index 0000000..59b78c2 --- /dev/null +++ b/cffi/verifier.py @@ -0,0 +1,306 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os, binascii, shutil, io +from . import __version_verifier_modules__ +from . import ffiplatform +from .error import VerificationError + +if sys.version_info >= (3, 3): + import importlib.machinery + def _extension_suffixes(): + return importlib.machinery.EXTENSION_SUFFIXES[:] +else: + import imp + def _extension_suffixes(): + return [suffix for suffix, _, type in imp.get_suffixes() + if type == imp.C_EXTENSION] + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super(NativeIO, self).write(s) + + +class Verifier(object): + + def __init__(self, ffi, preamble, tmpdir=None, modulename=None, + ext_package=None, tag='', force_generic_engine=False, + source_extension='.c', flags=None, relative_to=None, **kwds): + if ffi._parser._uses_new_feature: + raise VerificationError( + "feature not supported with ffi.verify(), but only " + "with ffi.set_source(): %s" % (ffi._parser._uses_new_feature,)) + self.ffi = ffi + self.preamble = preamble + if not modulename: + flattened_kwds = ffiplatform.flatten(kwds) + vengine_class = _locate_engine_class(ffi, force_generic_engine) + self._vengine = vengine_class(self) + self._vengine.patch_extension_kwds(kwds) + self.flags = flags + self.kwds = self.make_relative_to(kwds, relative_to) + # + if modulename: + if tag: + raise TypeError("can't specify both 'modulename' and 'tag'") + else: + key = '\x00'.join([sys.version[:3], __version_verifier_modules__, + preamble, flattened_kwds] + + ffi._cdefsources) + if sys.version_info >= (3,): + key = key.encode('utf-8') + k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) + k1 = k1.lstrip('0x').rstrip('L') + k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) + k2 = k2.lstrip('0').rstrip('L') + modulename = '_cffi_%s_%s%s%s' % (tag, self._vengine._class_key, + k1, k2) + suffix = _get_so_suffixes()[0] + self.tmpdir = tmpdir or _caller_dir_pycache() + self.sourcefilename = os.path.join(self.tmpdir, modulename + source_extension) + self.modulefilename = os.path.join(self.tmpdir, modulename + suffix) + self.ext_package = ext_package + self._has_source = False + self._has_module = False + + def write_source(self, file=None): + """Write the C source code. It is produced in 'self.sourcefilename', + which can be tweaked beforehand.""" + with self.ffi._lock: + if self._has_source and file is None: + raise VerificationError( + "source code already written") + self._write_source(file) + + def compile_module(self): + """Write the C source code (if not done already) and compile it. + This produces a dynamic link library in 'self.modulefilename'.""" + with self.ffi._lock: + if self._has_module: + raise VerificationError("module already compiled") + if not self._has_source: + self._write_source() + self._compile_module() + + def load_library(self): + """Get a C module from this Verifier instance. + Returns an instance of a FFILibrary class that behaves like the + objects returned by ffi.dlopen(), but that delegates all + operations to the C module. If necessary, the C code is written + and compiled first. + """ + with self.ffi._lock: + if not self._has_module: + self._locate_module() + if not self._has_module: + if not self._has_source: + self._write_source() + self._compile_module() + return self._load_library() + + def get_module_name(self): + basename = os.path.basename(self.modulefilename) + # kill both the .so extension and the other .'s, as introduced + # by Python 3: 'basename.cpython-33m.so' + basename = basename.split('.', 1)[0] + # and the _d added in Python 2 debug builds --- but try to be + # conservative and not kill a legitimate _d + if basename.endswith('_d') and hasattr(sys, 'gettotalrefcount'): + basename = basename[:-2] + return basename + + def get_extension(self): + ffiplatform._hack_at_distutils() # backward compatibility hack + if not self._has_source: + with self.ffi._lock: + if not self._has_source: + self._write_source() + sourcename = ffiplatform.maybe_relative_path(self.sourcefilename) + modname = self.get_module_name() + return ffiplatform.get_extension(sourcename, modname, **self.kwds) + + def generates_python_module(self): + return self._vengine._gen_python_module + + def make_relative_to(self, kwds, relative_to): + if relative_to and os.path.dirname(relative_to): + dirname = os.path.dirname(relative_to) + kwds = kwds.copy() + for key in ffiplatform.LIST_OF_FILE_NAMES: + if key in kwds: + lst = kwds[key] + if not isinstance(lst, (list, tuple)): + raise TypeError("keyword '%s' should be a list or tuple" + % (key,)) + lst = [os.path.join(dirname, fn) for fn in lst] + kwds[key] = lst + return kwds + + # ---------- + + def _locate_module(self): + if not os.path.isfile(self.modulefilename): + if self.ext_package: + try: + pkg = __import__(self.ext_package, None, None, ['__doc__']) + except ImportError: + return # cannot import the package itself, give up + # (e.g. it might be called differently before installation) + path = pkg.__path__ + else: + path = None + filename = self._vengine.find_module(self.get_module_name(), path, + _get_so_suffixes()) + if filename is None: + return + self.modulefilename = filename + self._vengine.collect_types() + self._has_module = True + + def _write_source_to(self, file): + self._vengine._f = file + try: + self._vengine.write_source_to_f() + finally: + del self._vengine._f + + def _write_source(self, file=None): + if file is not None: + self._write_source_to(file) + else: + # Write our source file to an in memory file. + f = NativeIO() + self._write_source_to(f) + source_data = f.getvalue() + + # Determine if this matches the current file + if os.path.exists(self.sourcefilename): + with open(self.sourcefilename, "r") as fp: + needs_written = not (fp.read() == source_data) + else: + needs_written = True + + # Actually write the file out if it doesn't match + if needs_written: + _ensure_dir(self.sourcefilename) + with open(self.sourcefilename, "w") as fp: + fp.write(source_data) + + # Set this flag + self._has_source = True + + def _compile_module(self): + # compile this C source + tmpdir = os.path.dirname(self.sourcefilename) + outputfilename = ffiplatform.compile(tmpdir, self.get_extension()) + try: + same = ffiplatform.samefile(outputfilename, self.modulefilename) + except OSError: + same = False + if not same: + _ensure_dir(self.modulefilename) + shutil.move(outputfilename, self.modulefilename) + self._has_module = True + + def _load_library(self): + assert self._has_module + if self.flags is not None: + return self._vengine.load_library(self.flags) + else: + return self._vengine.load_library() + +# ____________________________________________________________ + +_FORCE_GENERIC_ENGINE = False # for tests + +def _locate_engine_class(ffi, force_generic_engine): + if _FORCE_GENERIC_ENGINE: + force_generic_engine = True + if not force_generic_engine: + if '__pypy__' in sys.builtin_module_names: + force_generic_engine = True + else: + try: + import _cffi_backend + except ImportError: + _cffi_backend = '?' + if ffi._backend is not _cffi_backend: + force_generic_engine = True + if force_generic_engine: + from . import vengine_gen + return vengine_gen.VGenericEngine + else: + from . import vengine_cpy + return vengine_cpy.VCPythonEngine + +# ____________________________________________________________ + +_TMPDIR = None + +def _caller_dir_pycache(): + if _TMPDIR: + return _TMPDIR + result = os.environ.get('CFFI_TMPDIR') + if result: + return result + filename = sys._getframe(2).f_code.co_filename + return os.path.abspath(os.path.join(os.path.dirname(filename), + '__pycache__')) + +def set_tmpdir(dirname): + """Set the temporary directory to use instead of __pycache__.""" + global _TMPDIR + _TMPDIR = dirname + +def cleanup_tmpdir(tmpdir=None, keep_so=False): + """Clean up the temporary directory by removing all files in it + called `_cffi_*.{c,so}` as well as the `build` subdirectory.""" + tmpdir = tmpdir or _caller_dir_pycache() + try: + filelist = os.listdir(tmpdir) + except OSError: + return + if keep_so: + suffix = '.c' # only remove .c files + else: + suffix = _get_so_suffixes()[0].lower() + for fn in filelist: + if fn.lower().startswith('_cffi_') and ( + fn.lower().endswith(suffix) or fn.lower().endswith('.c')): + try: + os.unlink(os.path.join(tmpdir, fn)) + except OSError: + pass + clean_dir = [os.path.join(tmpdir, 'build')] + for dir in clean_dir: + try: + for fn in os.listdir(dir): + fn = os.path.join(dir, fn) + if os.path.isdir(fn): + clean_dir.append(fn) + else: + os.unlink(fn) + except OSError: + pass + +def _get_so_suffixes(): + suffixes = _extension_suffixes() + if not suffixes: + # bah, no C_EXTENSION available. Occurs on pypy without cpyext + if sys.platform == 'win32': + suffixes = [".pyd"] + else: + suffixes = [".so"] + + return suffixes + +def _ensure_dir(filename): + dirname = os.path.dirname(filename) + if dirname and not os.path.isdir(dirname): + os.makedirs(dirname) diff --git a/demo/_curses.py b/demo/_curses.py new file mode 100644 index 0000000..46443c2 --- /dev/null +++ b/demo/_curses.py @@ -0,0 +1,1075 @@ +"""Reimplementation of the standard extension module '_curses' using cffi.""" + +import sys +from functools import wraps + +from _curses_cffi import ffi, lib + + +def _copy_to_globals(name): + globals()[name] = getattr(lib, name) + + +def _setup(): + for name in ['ERR', 'OK', 'KEY_MIN', 'KEY_MAX', + 'A_ATTRIBUTES', 'A_NORMAL', 'A_STANDOUT', 'A_UNDERLINE', + 'A_REVERSE', 'A_BLINK', 'A_DIM', 'A_BOLD', 'A_ALTCHARSET', + 'A_PROTECT', 'A_CHARTEXT', 'A_COLOR', + 'COLOR_BLACK', 'COLOR_RED', 'COLOR_GREEN', 'COLOR_YELLOW', + 'COLOR_BLUE', 'COLOR_MAGENTA', 'COLOR_CYAN', 'COLOR_WHITE', + ]: + _copy_to_globals(name) + + if not lib._m_NetBSD: + _copy_to_globals('A_INVIS') + + for name in ['A_HORIZONTAL', 'A_LEFT', 'A_LOW', 'A_RIGHT', 'A_TOP', + 'A_VERTICAL', + ]: + if hasattr(lib, name): + _copy_to_globals(name) + + if lib._m_NCURSES_MOUSE_VERSION: + for name in ["BUTTON1_PRESSED", "BUTTON1_RELEASED", "BUTTON1_CLICKED", + "BUTTON1_DOUBLE_CLICKED", "BUTTON1_TRIPLE_CLICKED", + "BUTTON2_PRESSED", "BUTTON2_RELEASED", "BUTTON2_CLICKED", + "BUTTON2_DOUBLE_CLICKED", "BUTTON2_TRIPLE_CLICKED", + "BUTTON3_PRESSED", "BUTTON3_RELEASED", "BUTTON3_CLICKED", + "BUTTON3_DOUBLE_CLICKED", "BUTTON3_TRIPLE_CLICKED", + "BUTTON4_PRESSED", "BUTTON4_RELEASED", "BUTTON4_CLICKED", + "BUTTON4_DOUBLE_CLICKED", "BUTTON4_TRIPLE_CLICKED", + "BUTTON_SHIFT", "BUTTON_CTRL", "BUTTON_ALT", + "ALL_MOUSE_EVENTS", "REPORT_MOUSE_POSITION", + ]: + _copy_to_globals(name) + + if not lib._m_NetBSD: + for key in range(lib.KEY_MIN, lib.KEY_MAX): + key_n = lib.keyname(key) + if key_n == ffi.NULL: + continue + key_n = ffi.string(key_n) + if key_n == b"UNKNOWN KEY": + continue + if not isinstance(key_n, str): # python 3 + key_n = key_n.decode() + key_n = key_n.replace('(', '').replace(')', '') + globals()[key_n] = key + +_setup() + +# Do we want this? +# version = "2.2" +# __version__ = "2.2" + + +# ____________________________________________________________ + + +_initialised_setupterm = False +_initialised = False +_initialised_color = False + + +def _ensure_initialised_setupterm(): + if not _initialised_setupterm: + raise error("must call (at least) setupterm() first") + + +def _ensure_initialised(): + if not _initialised: + raise error("must call initscr() first") + + +def _ensure_initialised_color(): + if not _initialised and _initialised_color: + raise error("must call start_color() first") + + +def _check_ERR(code, fname): + if code != lib.ERR: + return None + elif fname is None: + raise error("curses function returned ERR") + else: + raise error("%s() returned ERR" % (fname,)) + + +def _check_NULL(rval): + if rval == ffi.NULL: + raise error("curses function returned NULL") + return rval + + +def _call_lib(method_name, *args): + return getattr(lib, method_name)(*args) + + +def _call_lib_check_ERR(method_name, *args): + return _check_ERR(_call_lib(method_name, *args), method_name) + + +def _mk_no_return(method_name): + def _execute(): + _ensure_initialised() + return _call_lib_check_ERR(method_name) + _execute.__name__ = method_name + return _execute + + +def _mk_flag_func(method_name): + # This is in the CPython implementation, but not documented anywhere. + # We have to support it, though, even if it make me sad. + def _execute(flag=True): + _ensure_initialised() + if flag: + return _call_lib_check_ERR(method_name) + else: + return _call_lib_check_ERR('no' + method_name) + _execute.__name__ = method_name + return _execute + + +def _mk_return_val(method_name): + def _execute(): + return _call_lib(method_name) + _execute.__name__ = method_name + return _execute + + +def _mk_w_getyx(method_name): + def _execute(self): + y = _call_lib(method_name + 'y', self._win) + x = _call_lib(method_name + 'x', self._win) + return (y, x) + _execute.__name__ = method_name + return _execute + + +def _mk_w_no_return(method_name): + def _execute(self, *args): + return _call_lib_check_ERR(method_name, self._win, *args) + _execute.__name__ = method_name + return _execute + + +def _mk_w_return_val(method_name): + def _execute(self, *args): + return _call_lib(method_name, self._win, *args) + _execute.__name__ = method_name + return _execute + + +def _chtype(ch): + return int(ffi.cast("chtype", ch)) + +def _texttype(text): + if isinstance(text, str): + return text + elif isinstance(text, unicode): + return str(text) # default encoding + else: + raise TypeError("str or unicode expected, got a '%s' object" + % (type(text).__name__,)) + + +def _extract_yx(args): + if len(args) >= 2: + return (args[0], args[1], args[2:]) + return (None, None, args) + + +def _process_args(funcname, args, count, optcount, frontopt=0): + outargs = [] + if frontopt: + if len(args) > count + optcount: + # We have the front optional args here. + outargs.extend(args[:frontopt]) + args = args[frontopt:] + else: + # No front optional args, so make them None. + outargs.extend([None] * frontopt) + if (len(args) < count) or (len(args) > count + optcount): + raise error("%s requires %s to %s arguments" % ( + funcname, count, count + optcount + frontopt)) + outargs.extend(args) + return outargs + + +def _argspec(count, optcount=0, frontopt=0): + def _argspec_deco(func): + @wraps(func) + def _wrapped(self, *args): + outargs = _process_args( + func.__name__, args, count, optcount, frontopt) + return func(self, *outargs) + return _wrapped + return _argspec_deco + + +# ____________________________________________________________ + + +class error(Exception): + pass + + +class Window(object): + def __init__(self, window): + self._win = window + + def __del__(self): + if self._win != lib.stdscr: + lib.delwin(self._win) + + untouchwin = _mk_w_no_return("untouchwin") + touchwin = _mk_w_no_return("touchwin") + redrawwin = _mk_w_no_return("redrawwin") + insertln = _mk_w_no_return("winsertln") + erase = _mk_w_no_return("werase") + deleteln = _mk_w_no_return("wdeleteln") + + is_wintouched = _mk_w_return_val("is_wintouched") + + syncdown = _mk_w_return_val("wsyncdown") + syncup = _mk_w_return_val("wsyncup") + standend = _mk_w_return_val("wstandend") + standout = _mk_w_return_val("wstandout") + cursyncup = _mk_w_return_val("wcursyncup") + clrtoeol = _mk_w_return_val("wclrtoeol") + clrtobot = _mk_w_return_val("wclrtobot") + clear = _mk_w_return_val("wclear") + + idcok = _mk_w_no_return("idcok") + immedok = _mk_w_no_return("immedok") + timeout = _mk_w_no_return("wtimeout") + + getyx = _mk_w_getyx("getcur") + getbegyx = _mk_w_getyx("getbeg") + getmaxyx = _mk_w_getyx("getmax") + getparyx = _mk_w_getyx("getpar") + + clearok = _mk_w_no_return("clearok") + idlok = _mk_w_no_return("idlok") + leaveok = _mk_w_no_return("leaveok") + notimeout = _mk_w_no_return("notimeout") + scrollok = _mk_w_no_return("scrollok") + insdelln = _mk_w_no_return("winsdelln") + syncok = _mk_w_no_return("syncok") + + mvwin = _mk_w_no_return("mvwin") + mvderwin = _mk_w_no_return("mvderwin") + move = _mk_w_no_return("wmove") + + if not lib._m_STRICT_SYSV_CURSES: + resize = _mk_w_no_return("wresize") + + if lib._m_NetBSD: + keypad = _mk_w_return_val("keypad") + nodelay = _mk_w_return_val("nodelay") + else: + keypad = _mk_w_no_return("keypad") + nodelay = _mk_w_no_return("nodelay") + + @_argspec(1, 1, 2) + def addch(self, y, x, ch, attr=None): + if attr is None: + attr = lib.A_NORMAL + ch = _chtype(ch) + + if y is not None: + code = lib.mvwaddch(self._win, y, x, ch | attr) + else: + code = lib.waddch(self._win, ch | attr) + return _check_ERR(code, "addch") + + @_argspec(1, 1, 2) + def addstr(self, y, x, text, attr=None): + text = _texttype(text) + if attr is not None: + attr_old = lib.getattrs(self._win) + lib.wattrset(self._win, attr) + if y is not None: + code = lib.mvwaddstr(self._win, y, x, text) + else: + code = lib.waddstr(self._win, text) + if attr is not None: + lib.wattrset(self._win, attr_old) + return _check_ERR(code, "addstr") + + @_argspec(2, 1, 2) + def addnstr(self, y, x, text, n, attr=None): + text = _texttype(text) + if attr is not None: + attr_old = lib.getattrs(self._win) + lib.wattrset(self._win, attr) + if y is not None: + code = lib.mvwaddnstr(self._win, y, x, text, n) + else: + code = lib.waddnstr(self._win, text, n) + if attr is not None: + lib.wattrset(self._win, attr_old) + return _check_ERR(code, "addnstr") + + def bkgd(self, ch, attr=None): + if attr is None: + attr = lib.A_NORMAL + return _check_ERR(lib.wbkgd(self._win, _chtype(ch) | attr), "bkgd") + + attroff = _mk_w_no_return("wattroff") + attron = _mk_w_no_return("wattron") + attrset = _mk_w_no_return("wattrset") + + def bkgdset(self, ch, attr=None): + if attr is None: + attr = lib.A_NORMAL + lib.wbkgdset(self._win, _chtype(ch) | attr) + return None + + def border(self, ls=0, rs=0, ts=0, bs=0, tl=0, tr=0, bl=0, br=0): + lib.wborder(self._win, + _chtype(ls), _chtype(rs), _chtype(ts), _chtype(bs), + _chtype(tl), _chtype(tr), _chtype(bl), _chtype(br)) + return None + + def box(self, vertint=0, horint=0): + lib.box(self._win, vertint, horint) + return None + + @_argspec(1, 1, 2) + def chgat(self, y, x, num, attr=None): + # These optional args are in a weird order. + if attr is None: + attr = num + num = -1 + + color = ((attr >> 8) & 0xff) + attr = attr - (color << 8) + + if y is not None: + code = lib.mvwchgat(self._win, y, x, num, attr, color, ffi.NULL) + lib.touchline(self._win, y, 1) + else: + yy, _ = self.getyx() + code = lib.wchgat(self._win, num, attr, color, ffi.NULL) + lib.touchline(self._win, yy, 1) + return _check_ERR(code, "chgat") + + def delch(self, *args): + if len(args) == 0: + code = lib.wdelch(self._win) + elif len(args) == 2: + code = lib.mvwdelch(self._win, *args) + else: + raise error("delch requires 0 or 2 arguments") + return _check_ERR(code, "[mv]wdelch") + + def derwin(self, *args): + nlines = 0 + ncols = 0 + if len(args) == 2: + begin_y, begin_x = args + elif len(args) == 4: + nlines, ncols, begin_y, begin_x = args + else: + raise error("derwin requires 2 or 4 arguments") + + win = lib.derwin(self._win, nlines, ncols, begin_y, begin_x) + return Window(_check_NULL(win)) + + def echochar(self, ch, attr=None): + if attr is None: + attr = lib.A_NORMAL + ch = _chtype(ch) + + if lib._m_ispad(self._win): + code = lib.pechochar(self._win, ch | attr) + else: + code = lib.wechochar(self._win, ch | attr) + return _check_ERR(code, "echochar") + + if lib._m_NCURSES_MOUSE_VERSION: + enclose = _mk_w_return_val("wenclose") + + getbkgd = _mk_w_return_val("getbkgd") + + def getch(self, *args): + if len(args) == 0: + val = lib.wgetch(self._win) + elif len(args) == 2: + val = lib.mvwgetch(self._win, *args) + else: + raise error("getch requires 0 or 2 arguments") + return val + + def getkey(self, *args): + if len(args) == 0: + val = lib.wgetch(self._win) + elif len(args) == 2: + val = lib.mvwgetch(self._win, *args) + else: + raise error("getkey requires 0 or 2 arguments") + + if val == lib.ERR: + raise error("no input") + elif val <= 255: + return chr(val) + else: + # XXX: The following line is different if `__NetBSD__` is defined. + val = lib.keyname(val) + if val == ffi.NULL: + return "" + return ffi.string(val) + + @_argspec(0, 1, 2) + def getstr(self, y, x, n=1023): + n = min(n, 1023) + buf = ffi.new("char[1024]") # /* This should be big enough.. I hope */ + + if y is None: + val = lib.wgetnstr(self._win, buf, n) + else: + val = lib.mvwgetnstr(self._win, y, x, buf, n) + + if val == lib.ERR: + return "" + return ffi.string(buf) + + @_argspec(2, 1, 2) + def hline(self, y, x, ch, n, attr=None): + ch = _chtype(ch) + if attr is None: + attr = lib.A_NORMAL + if y is not None: + _check_ERR(lib.wmove(self._win, y, x), "wmove") + return _check_ERR(lib.whline(self._win, ch | attr, n), "hline") + + @_argspec(1, 1, 2) + def insch(self, y, x, ch, attr=None): + ch = _chtype(ch) + if attr is None: + attr = lib.A_NORMAL + if y is not None: + code = lib.mvwinsch(self._win, y, x, ch | attr) + else: + code = lib.winsch(self._win, ch | attr) + return _check_ERR(code, "insch") + + def inch(self, *args): + if len(args) == 0: + return lib.winch(self._win) + elif len(args) == 2: + return lib.mvwinch(self._win, *args) + else: + raise error("inch requires 0 or 2 arguments") + + @_argspec(0, 1, 2) + def instr(self, y, x, n=1023): + n = min(n, 1023) + buf = ffi.new("char[1024]") # /* This should be big enough.. I hope */ + if y is None: + code = lib.winnstr(self._win, buf, n) + else: + code = lib.mvwinnstr(self._win, y, x, buf, n) + + if code == lib.ERR: + return "" + return ffi.string(buf) + + @_argspec(1, 1, 2) + def insstr(self, y, x, text, attr=None): + text = _texttype(text) + if attr is not None: + attr_old = lib.getattrs(self._win) + lib.wattrset(self._win, attr) + if y is not None: + code = lib.mvwinsstr(self._win, y, x, text) + else: + code = lib.winsstr(self._win, text) + if attr is not None: + lib.wattrset(self._win, attr_old) + return _check_ERR(code, "insstr") + + @_argspec(2, 1, 2) + def insnstr(self, y, x, text, n, attr=None): + text = _texttype(text) + if attr is not None: + attr_old = lib.getattrs(self._win) + lib.wattrset(self._win, attr) + if y is not None: + code = lib.mvwinsnstr(self._win, y, x, text, n) + else: + code = lib.winsnstr(self._win, text, n) + if attr is not None: + lib.wattrset(self._win, attr_old) + return _check_ERR(code, "insnstr") + + def is_linetouched(self, line): + code = lib.is_linetouched(self._win, line) + if code == lib.ERR: + raise error("is_linetouched: line number outside of boundaries") + if code == lib.FALSE: + return False + return True + + def noutrefresh(self, *args): + if lib._m_ispad(self._win): + if len(args) != 6: + raise error( + "noutrefresh() called for a pad requires 6 arguments") + return _check_ERR(lib.pnoutrefresh(self._win, *args), + "pnoutrefresh") + else: + # XXX: Better args check here? We need zero args. + return _check_ERR(lib.wnoutrefresh(self._win, *args), + "wnoutrefresh") + + nooutrefresh = noutrefresh # "to be removed in 2.3", but in 2.7, 3.x. + + def _copywin(self, dstwin, overlay, + sminr, sminc, dminr, dminc, dmaxr, dmaxc): + return _check_ERR(lib.copywin(self._win, dstwin._win, + sminr, sminc, dminr, dminc, dmaxr, dmaxc, + overlay), "copywin") + + def overlay(self, dstwin, *args): + if len(args) == 6: + return self._copywin(dstwin, True, *args) + elif len(args) == 0: + return _check_ERR(lib.overlay(self._win, dstwin._win), "overlay") + else: + raise error("overlay requires one or seven arguments") + + def overwrite(self, dstwin, *args): + if len(args) == 6: + return self._copywin(dstwin, False, *args) + elif len(args) == 0: + return _check_ERR(lib.overwrite(self._win, dstwin._win), + "overwrite") + else: + raise error("overwrite requires one or seven arguments") + + def putwin(self, filep): + # filestar = ffi.new("FILE *", filep) + return _check_ERR(lib.putwin(self._win, filep), "putwin") + + def redrawln(self, beg, num): + return _check_ERR(lib.wredrawln(self._win, beg, num), "redrawln") + + def refresh(self, *args): + if lib._m_ispad(self._win): + if len(args) != 6: + raise error( + "noutrefresh() called for a pad requires 6 arguments") + return _check_ERR(lib.prefresh(self._win, *args), "prefresh") + else: + # XXX: Better args check here? We need zero args. + return _check_ERR(lib.wrefresh(self._win, *args), "wrefresh") + + def setscrreg(self, y, x): + return _check_ERR(lib.wsetscrreg(self._win, y, x), "wsetscrreg") + + def subwin(self, *args): + nlines = 0 + ncols = 0 + if len(args) == 2: + begin_y, begin_x = args + elif len(args) == 4: + nlines, ncols, begin_y, begin_x = args + else: + raise error("subwin requires 2 or 4 arguments") + + if lib._m_ispad(self._win): + win = lib.subpad(self._win, nlines, ncols, begin_y, begin_x) + else: + win = lib.subwin(self._win, nlines, ncols, begin_y, begin_x) + return Window(_check_NULL(win)) + + def scroll(self, nlines=None): + if nlines is None: + return _check_ERR(lib.scroll(self._win), "scroll") + else: + return _check_ERR(lib.wscrl(self._win, nlines), "scroll") + + def touchline(self, st, cnt, val=None): + if val is None: + return _check_ERR(lib.touchline(self._win, st, cnt), "touchline") + else: + return _check_ERR(lib.wtouchln(self._win, st, cnt, val), + "touchline") + + @_argspec(2, 1, 2) + def vline(self, y, x, ch, n, attr=None): + ch = _chtype(ch) + if attr is None: + attr = lib.A_NORMAL + if y is not None: + _check_ERR(lib.wmove(self._win, y, x), "wmove") + return _check_ERR(lib.wvline(self._win, ch | attr, n), "vline") + + +beep = _mk_no_return("beep") +def_prog_mode = _mk_no_return("def_prog_mode") +def_shell_mode = _mk_no_return("def_shell_mode") +doupdate = _mk_no_return("doupdate") +endwin = _mk_no_return("endwin") +flash = _mk_no_return("flash") +nocbreak = _mk_no_return("nocbreak") +noecho = _mk_no_return("noecho") +nonl = _mk_no_return("nonl") +noraw = _mk_no_return("noraw") +reset_prog_mode = _mk_no_return("reset_prog_mode") +reset_shell_mode = _mk_no_return("reset_shell_mode") +resetty = _mk_no_return("resetty") +savetty = _mk_no_return("savetty") + +cbreak = _mk_flag_func("cbreak") +echo = _mk_flag_func("echo") +nl = _mk_flag_func("nl") +raw = _mk_flag_func("raw") + +baudrate = _mk_return_val("baudrate") +termattrs = _mk_return_val("termattrs") + +termname = _mk_return_val("termname") +longname = _mk_return_val("longname") + +can_change_color = _mk_return_val("can_change_color") +has_colors = _mk_return_val("has_colors") +has_ic = _mk_return_val("has_ic") +has_il = _mk_return_val("has_il") +isendwin = _mk_return_val("isendwin") +flushinp = _mk_return_val("flushinp") +noqiflush = _mk_return_val("noqiflush") + + +def filter(): + lib.filter() + return None + + +def color_content(color): + _ensure_initialised_color() + r, g, b = ffi.new("short *"), ffi.new("short *"), ffi.new("short *") + if lib.color_content(color, r, g, b) == lib.ERR: + raise error("Argument 1 was out of range. Check value of COLORS.") + return (r[0], g[0], b[0]) + + +def color_pair(n): + _ensure_initialised_color() + return (n << 8) + + +def curs_set(vis): + _ensure_initialised() + val = lib.curs_set(vis) + _check_ERR(val, "curs_set") + return val + + +def delay_output(ms): + _ensure_initialised() + return _check_ERR(lib.delay_output(ms), "delay_output") + + +def erasechar(): + _ensure_initialised() + return lib.erasechar() + + +def getsyx(): + _ensure_initialised() + yx = ffi.new("int[2]") + lib._m_getsyx(yx) + return (yx[0], yx[1]) + + +if lib._m_NCURSES_MOUSE_VERSION: + + def getmouse(): + _ensure_initialised() + mevent = ffi.new("MEVENT *") + _check_ERR(lib.getmouse(mevent), "getmouse") + return (mevent.id, mevent.x, mevent.y, mevent.z, mevent.bstate) + + def ungetmouse(id, x, y, z, bstate): + _ensure_initialised() + mevent = ffi.new("MEVENT *") + mevent.id, mevent.x, mevent.y, mevent.z, mevent.bstate = ( + id, x, y, z, bstate) + return _check_ERR(lib.ungetmouse(mevent), "ungetmouse") + + +def getwin(filep): + return Window(_check_NULL(lib.getwin(filep))) + + +def halfdelay(tenths): + _ensure_initialised() + return _check_ERR(lib.halfdelay(tenths), "halfdelay") + + +if not lib._m_STRICT_SYSV_CURSES: + def has_key(ch): + _ensure_initialised() + return lib.has_key(ch) + + +def init_color(color, r, g, b): + _ensure_initialised_color() + return _check_ERR(lib.init_color(color, r, g, b), "init_color") + + +def init_pair(pair, f, b): + _ensure_initialised_color() + return _check_ERR(lib.init_pair(pair, f, b), "init_pair") + + +def _mk_acs(name, ichar): + if len(ichar) == 1: + globals()[name] = lib.acs_map[ord(ichar)] + else: + globals()[name] = globals()[ichar] + + +def _map_acs(): + _mk_acs("ACS_ULCORNER", 'l') + _mk_acs("ACS_LLCORNER", 'm') + _mk_acs("ACS_URCORNER", 'k') + _mk_acs("ACS_LRCORNER", 'j') + _mk_acs("ACS_LTEE", 't') + _mk_acs("ACS_RTEE", 'u') + _mk_acs("ACS_BTEE", 'v') + _mk_acs("ACS_TTEE", 'w') + _mk_acs("ACS_HLINE", 'q') + _mk_acs("ACS_VLINE", 'x') + _mk_acs("ACS_PLUS", 'n') + _mk_acs("ACS_S1", 'o') + _mk_acs("ACS_S9", 's') + _mk_acs("ACS_DIAMOND", '`') + _mk_acs("ACS_CKBOARD", 'a') + _mk_acs("ACS_DEGREE", 'f') + _mk_acs("ACS_PLMINUS", 'g') + _mk_acs("ACS_BULLET", '~') + _mk_acs("ACS_LARROW", ',') + _mk_acs("ACS_RARROW", '+') + _mk_acs("ACS_DARROW", '.') + _mk_acs("ACS_UARROW", '-') + _mk_acs("ACS_BOARD", 'h') + _mk_acs("ACS_LANTERN", 'i') + _mk_acs("ACS_BLOCK", '0') + _mk_acs("ACS_S3", 'p') + _mk_acs("ACS_S7", 'r') + _mk_acs("ACS_LEQUAL", 'y') + _mk_acs("ACS_GEQUAL", 'z') + _mk_acs("ACS_PI", '{') + _mk_acs("ACS_NEQUAL", '|') + _mk_acs("ACS_STERLING", '}') + _mk_acs("ACS_BSSB", "ACS_ULCORNER") + _mk_acs("ACS_SSBB", "ACS_LLCORNER") + _mk_acs("ACS_BBSS", "ACS_URCORNER") + _mk_acs("ACS_SBBS", "ACS_LRCORNER") + _mk_acs("ACS_SBSS", "ACS_RTEE") + _mk_acs("ACS_SSSB", "ACS_LTEE") + _mk_acs("ACS_SSBS", "ACS_BTEE") + _mk_acs("ACS_BSSS", "ACS_TTEE") + _mk_acs("ACS_BSBS", "ACS_HLINE") + _mk_acs("ACS_SBSB", "ACS_VLINE") + _mk_acs("ACS_SSSS", "ACS_PLUS") + + +def initscr(): + if _initialised: + lib.wrefresh(lib.stdscr) + return Window(lib.stdscr) + + win = _check_NULL(lib.initscr()) + globals()['_initialised_setupterm'] = True + globals()['_initialised'] = True + + _map_acs() + + globals()["LINES"] = lib.LINES + globals()["COLS"] = lib.COLS + + return Window(win) + + +def setupterm(term=None, fd=-1): + if fd == -1: + # XXX: Check for missing stdout here? + fd = sys.stdout.fileno() + + if _initialised_setupterm: + return None + + if term is None: + term = ffi.NULL + err = ffi.new("int *") + if lib.setupterm(term, fd, err) == lib.ERR: + err = err[0] + if err == 0: + raise error("setupterm: could not find terminal") + elif err == -1: + raise error("setupterm: could not find terminfo database") + else: + raise error("setupterm: unknown error") + + globals()["_initialised_setupterm"] = True + return None + + +def intrflush(ch): + _ensure_initialised() + return _check_ERR(lib.intrflush(ffi.NULL, ch), "intrflush") + + +# XXX: #ifdef HAVE_CURSES_IS_TERM_RESIZED +def is_term_resized(lines, columns): + _ensure_initialised() + return lib.is_term_resized(lines, columns) + + +if not lib._m_NetBSD: + def keyname(ch): + _ensure_initialised() + if ch < 0: + raise error("invalid key number") + knp = lib.keyname(ch) + if knp == ffi.NULL: + return "" + return ffi.string(knp) + + +def killchar(): + return lib.killchar() + + +def meta(ch): + return _check_ERR(lib.meta(lib.stdscr, ch), "meta") + + +if lib._m_NCURSES_MOUSE_VERSION: + + def mouseinterval(interval): + _ensure_initialised() + return _check_ERR(lib.mouseinterval(interval), "mouseinterval") + + def mousemask(newmask): + _ensure_initialised() + oldmask = ffi.new("mmask_t *") + availmask = lib.mousemask(newmask, oldmask) + return (availmask, oldmask) + + +def napms(ms): + _ensure_initialised() + return lib.napms(ms) + + +def newpad(nlines, ncols): + _ensure_initialised() + return Window(_check_NULL(lib.newpad(nlines, ncols))) + + +def newwin(nlines, ncols, begin_y=None, begin_x=None): + _ensure_initialised() + if begin_x is None: + if begin_y is not None: + raise error("newwin requires 2 or 4 arguments") + begin_y = begin_x = 0 + + return Window(_check_NULL(lib.newwin(nlines, ncols, begin_y, begin_x))) + + +def pair_content(pair): + _ensure_initialised_color() + f = ffi.new("short *") + b = ffi.new("short *") + if lib.pair_content(pair, f, b) == lib.ERR: + raise error("Argument 1 was out of range. (1..COLOR_PAIRS-1)") + return (f, b) + + +def pair_number(pairvalue): + _ensure_initialised_color() + return (pairvalue & lib.A_COLOR) >> 8 + + +def putp(text): + text = _texttype(text) + return _check_ERR(lib.putp(text), "putp") + + +def qiflush(flag=True): + _ensure_initialised() + if flag: + lib.qiflush() + else: + lib.noqiflush() + return None + + +# XXX: Do something about the following? +# /* Internal helper used for updating curses.LINES, curses.COLS, _curses.LINES +# * and _curses.COLS */ +# #if defined(HAVE_CURSES_RESIZETERM) || defined(HAVE_CURSES_RESIZE_TERM) +# static int +# update_lines_cols(void) +# { +# PyObject *o; +# PyObject *m = PyImport_ImportModuleNoBlock("curses"); + +# if (!m) +# return 0; + +# o = PyInt_FromLong(LINES); +# if (!o) { +# Py_DECREF(m); +# return 0; +# } +# if (PyObject_SetAttrString(m, "LINES", o)) { +# Py_DECREF(m); +# Py_DECREF(o); +# return 0; +# } +# if (PyDict_SetItemString(ModDict, "LINES", o)) { +# Py_DECREF(m); +# Py_DECREF(o); +# return 0; +# } +# Py_DECREF(o); +# o = PyInt_FromLong(COLS); +# if (!o) { +# Py_DECREF(m); +# return 0; +# } +# if (PyObject_SetAttrString(m, "COLS", o)) { +# Py_DECREF(m); +# Py_DECREF(o); +# return 0; +# } +# if (PyDict_SetItemString(ModDict, "COLS", o)) { +# Py_DECREF(m); +# Py_DECREF(o); +# return 0; +# } +# Py_DECREF(o); +# Py_DECREF(m); +# return 1; +# } +# #endif + +# #ifdef HAVE_CURSES_RESIZETERM +# static PyObject * +# PyCurses_ResizeTerm(PyObject *self, PyObject *args) +# { +# int lines; +# int columns; +# PyObject *result; + +# PyCursesInitialised; + +# if (!PyArg_ParseTuple(args,"ii:resizeterm", &lines, &columns)) +# return NULL; + +# result = PyCursesCheckERR(resizeterm(lines, columns), "resizeterm"); +# if (!result) +# return NULL; +# if (!update_lines_cols()) +# return NULL; +# return result; +# } + +# #endif + +# #ifdef HAVE_CURSES_RESIZE_TERM +# static PyObject * +# PyCurses_Resize_Term(PyObject *self, PyObject *args) +# { +# int lines; +# int columns; + +# PyObject *result; + +# PyCursesInitialised; + +# if (!PyArg_ParseTuple(args,"ii:resize_term", &lines, &columns)) +# return NULL; + +# result = PyCursesCheckERR(resize_term(lines, columns), "resize_term"); +# if (!result) +# return NULL; +# if (!update_lines_cols()) +# return NULL; +# return result; +# } +# #endif /* HAVE_CURSES_RESIZE_TERM */ + + +def setsyx(y, x): + _ensure_initialised() + lib.setsyx(y, x) + return None + + +def start_color(): + _check_ERR(lib.start_color(), "start_color") + globals()["COLORS"] = lib.COLORS + globals()["COLOR_PAIRS"] = lib.COLOR_PAIRS + globals()["_initialised_color"] = True + return None + + +def tigetflag(capname): + _ensure_initialised_setupterm() + return lib.tigetflag(capname) + + +def tigetnum(capname): + _ensure_initialised_setupterm() + return lib.tigetnum(capname) + + +def tigetstr(capname): + _ensure_initialised_setupterm() + val = lib.tigetstr(capname) + if int(ffi.cast("intptr_t", val)) in (0, -1): + return None + return ffi.string(val) + + +def tparm(fmt, i1=0, i2=0, i3=0, i4=0, i5=0, i6=0, i7=0, i8=0, i9=0): + args = [ffi.cast("int", i) for i in (i1, i2, i3, i4, i5, i6, i7, i8, i9)] + result = lib.tparm(fmt, *args) + if result == ffi.NULL: + raise error("tparm() returned NULL") + return ffi.string(result) + + +def typeahead(fd): + _ensure_initialised() + return _check_ERR(lib.typeahead(fd), "typeahead") + + +def unctrl(ch): + _ensure_initialised() + return lib.unctrl(_chtype(ch)) + + +def ungetch(ch): + _ensure_initialised() + return _check_ERR(lib.ungetch(_chtype(ch)), "ungetch") + + +def use_env(flag): + lib.use_env(flag) + return None + + +if not lib._m_STRICT_SYSV_CURSES: + + def use_default_colors(): + _ensure_initialised_color() + return _check_ERR(lib.use_default_colors(), "use_default_colors") diff --git a/demo/_curses_build.py b/demo/_curses_build.py new file mode 100644 index 0000000..1a1a3ec --- /dev/null +++ b/demo/_curses_build.py @@ -0,0 +1,327 @@ +import sys +if sys.platform == 'win32': + #This module does not exist in windows + raise ImportError('No module named _curses') + +from cffi import FFI + +ffi = FFI() + +ffi.cdef(""" +typedef ... WINDOW; +typedef ... SCREEN; +typedef unsigned long... mmask_t; +typedef unsigned char bool; +typedef unsigned long... chtype; +typedef chtype attr_t; + +typedef struct +{ + short id; /* ID to distinguish multiple devices */ + int x, y, z; /* event coordinates (character-cell) */ + mmask_t bstate; /* button state bits */ +} +MEVENT; + +static const int ERR, OK; +static const int TRUE, FALSE; +static const int KEY_MIN, KEY_MAX; + +static const int COLOR_BLACK; +static const int COLOR_RED; +static const int COLOR_GREEN; +static const int COLOR_YELLOW; +static const int COLOR_BLUE; +static const int COLOR_MAGENTA; +static const int COLOR_CYAN; +static const int COLOR_WHITE; + +static const chtype A_ATTRIBUTES; +static const chtype A_NORMAL; +static const chtype A_STANDOUT; +static const chtype A_UNDERLINE; +static const chtype A_REVERSE; +static const chtype A_BLINK; +static const chtype A_DIM; +static const chtype A_BOLD; +static const chtype A_ALTCHARSET; +static const chtype A_INVIS; +static const chtype A_PROTECT; +static const chtype A_CHARTEXT; +static const chtype A_COLOR; + +static const int BUTTON1_RELEASED; +static const int BUTTON1_PRESSED; +static const int BUTTON1_CLICKED; +static const int BUTTON1_DOUBLE_CLICKED; +static const int BUTTON1_TRIPLE_CLICKED; +static const int BUTTON2_RELEASED; +static const int BUTTON2_PRESSED; +static const int BUTTON2_CLICKED; +static const int BUTTON2_DOUBLE_CLICKED; +static const int BUTTON2_TRIPLE_CLICKED; +static const int BUTTON3_RELEASED; +static const int BUTTON3_PRESSED; +static const int BUTTON3_CLICKED; +static const int BUTTON3_DOUBLE_CLICKED; +static const int BUTTON3_TRIPLE_CLICKED; +static const int BUTTON4_RELEASED; +static const int BUTTON4_PRESSED; +static const int BUTTON4_CLICKED; +static const int BUTTON4_DOUBLE_CLICKED; +static const int BUTTON4_TRIPLE_CLICKED; +static const int BUTTON_SHIFT; +static const int BUTTON_CTRL; +static const int BUTTON_ALT; +static const int ALL_MOUSE_EVENTS; +static const int REPORT_MOUSE_POSITION; + +int setupterm(char *, int, int *); + +WINDOW *stdscr; +int COLORS; +int COLOR_PAIRS; +int COLS; +int LINES; + +int baudrate(void); +int beep(void); +int box(WINDOW *, chtype, chtype); +bool can_change_color(void); +int cbreak(void); +int clearok(WINDOW *, bool); +int color_content(short, short*, short*, short*); +int copywin(const WINDOW*, WINDOW*, int, int, int, int, int, int, int); +int curs_set(int); +int def_prog_mode(void); +int def_shell_mode(void); +int delay_output(int); +int delwin(WINDOW *); +WINDOW * derwin(WINDOW *, int, int, int, int); +int doupdate(void); +int echo(void); +int endwin(void); +char erasechar(void); +void filter(void); +int flash(void); +int flushinp(void); +chtype getbkgd(WINDOW *); +WINDOW * getwin(FILE *); +int halfdelay(int); +bool has_colors(void); +bool has_ic(void); +bool has_il(void); +void idcok(WINDOW *, bool); +int idlok(WINDOW *, bool); +void immedok(WINDOW *, bool); +WINDOW * initscr(void); +int init_color(short, short, short, short); +int init_pair(short, short, short); +int intrflush(WINDOW *, bool); +bool isendwin(void); +bool is_linetouched(WINDOW *, int); +bool is_wintouched(WINDOW *); +const char * keyname(int); +int keypad(WINDOW *, bool); +char killchar(void); +int leaveok(WINDOW *, bool); +char * longname(void); +int meta(WINDOW *, bool); +int mvderwin(WINDOW *, int, int); +int mvwaddch(WINDOW *, int, int, const chtype); +int mvwaddnstr(WINDOW *, int, int, const char *, int); +int mvwaddstr(WINDOW *, int, int, const char *); +int mvwchgat(WINDOW *, int, int, int, attr_t, short, const void *); +int mvwdelch(WINDOW *, int, int); +int mvwgetch(WINDOW *, int, int); +int mvwgetnstr(WINDOW *, int, int, char *, int); +int mvwin(WINDOW *, int, int); +chtype mvwinch(WINDOW *, int, int); +int mvwinnstr(WINDOW *, int, int, char *, int); +int mvwinsch(WINDOW *, int, int, chtype); +int mvwinsnstr(WINDOW *, int, int, const char *, int); +int mvwinsstr(WINDOW *, int, int, const char *); +int napms(int); +WINDOW * newpad(int, int); +WINDOW * newwin(int, int, int, int); +int nl(void); +int nocbreak(void); +int nodelay(WINDOW *, bool); +int noecho(void); +int nonl(void); +void noqiflush(void); +int noraw(void); +int notimeout(WINDOW *, bool); +int overlay(const WINDOW*, WINDOW *); +int overwrite(const WINDOW*, WINDOW *); +int pair_content(short, short*, short*); +int pechochar(WINDOW *, const chtype); +int pnoutrefresh(WINDOW*, int, int, int, int, int, int); +int prefresh(WINDOW *, int, int, int, int, int, int); +int putwin(WINDOW *, FILE *); +void qiflush(void); +int raw(void); +int redrawwin(WINDOW *); +int resetty(void); +int reset_prog_mode(void); +int reset_shell_mode(void); +int savetty(void); +int scroll(WINDOW *); +int scrollok(WINDOW *, bool); +int start_color(void); +WINDOW * subpad(WINDOW *, int, int, int, int); +WINDOW * subwin(WINDOW *, int, int, int, int); +int syncok(WINDOW *, bool); +chtype termattrs(void); +char * termname(void); +int touchline(WINDOW *, int, int); +int touchwin(WINDOW *); +int typeahead(int); +int ungetch(int); +int untouchwin(WINDOW *); +void use_env(bool); +int waddch(WINDOW *, const chtype); +int waddnstr(WINDOW *, const char *, int); +int waddstr(WINDOW *, const char *); +int wattron(WINDOW *, int); +int wattroff(WINDOW *, int); +int wattrset(WINDOW *, int); +int wbkgd(WINDOW *, chtype); +void wbkgdset(WINDOW *, chtype); +int wborder(WINDOW *, chtype, chtype, chtype, chtype, + chtype, chtype, chtype, chtype); +int wchgat(WINDOW *, int, attr_t, short, const void *); +int wclear(WINDOW *); +int wclrtobot(WINDOW *); +int wclrtoeol(WINDOW *); +void wcursyncup(WINDOW *); +int wdelch(WINDOW *); +int wdeleteln(WINDOW *); +int wechochar(WINDOW *, const chtype); +int werase(WINDOW *); +int wgetch(WINDOW *); +int wgetnstr(WINDOW *, char *, int); +int whline(WINDOW *, chtype, int); +chtype winch(WINDOW *); +int winnstr(WINDOW *, char *, int); +int winsch(WINDOW *, chtype); +int winsdelln(WINDOW *, int); +int winsertln(WINDOW *); +int winsnstr(WINDOW *, const char *, int); +int winsstr(WINDOW *, const char *); +int wmove(WINDOW *, int, int); +int wresize(WINDOW *, int, int); +int wnoutrefresh(WINDOW *); +int wredrawln(WINDOW *, int, int); +int wrefresh(WINDOW *); +int wscrl(WINDOW *, int); +int wsetscrreg(WINDOW *, int, int); +int wstandout(WINDOW *); +int wstandend(WINDOW *); +void wsyncdown(WINDOW *); +void wsyncup(WINDOW *); +void wtimeout(WINDOW *, int); +int wtouchln(WINDOW *, int, int, int); +int wvline(WINDOW *, chtype, int); +int tigetflag(char *); +int tigetnum(char *); +char * tigetstr(char *); +int putp(const char *); +char * tparm(const char *, ...); +int getattrs(const WINDOW *); +int getcurx(const WINDOW *); +int getcury(const WINDOW *); +int getbegx(const WINDOW *); +int getbegy(const WINDOW *); +int getmaxx(const WINDOW *); +int getmaxy(const WINDOW *); +int getparx(const WINDOW *); +int getpary(const WINDOW *); + +int getmouse(MEVENT *); +int ungetmouse(MEVENT *); +mmask_t mousemask(mmask_t, mmask_t *); +bool wenclose(const WINDOW *, int, int); +int mouseinterval(int); + +void setsyx(int y, int x); +const char *unctrl(chtype); +int use_default_colors(void); + +int has_key(int); +bool is_term_resized(int, int); + +#define _m_STRICT_SYSV_CURSES ... +#define _m_NCURSES_MOUSE_VERSION ... +#define _m_NetBSD ... +int _m_ispad(WINDOW *); + +chtype acs_map[]; + +// For _curses_panel: + +typedef ... PANEL; + +WINDOW *panel_window(const PANEL *); +void update_panels(void); +int hide_panel(PANEL *); +int show_panel(PANEL *); +int del_panel(PANEL *); +int top_panel(PANEL *); +int bottom_panel(PANEL *); +PANEL *new_panel(WINDOW *); +PANEL *panel_above(const PANEL *); +PANEL *panel_below(const PANEL *); +int set_panel_userptr(PANEL *, void *); +const void *panel_userptr(const PANEL *); +int move_panel(PANEL *, int, int); +int replace_panel(PANEL *,WINDOW *); +int panel_hidden(const PANEL *); + +void _m_getsyx(int *yx); +""") + + +ffi.set_source("_curses_cffi", """ +#ifdef __APPLE__ +/* the following define is necessary for OS X 10.6+; without it, the + Apple-supplied ncurses.h sets NCURSES_OPAQUE to 1, and then Python + can't get at the WINDOW flags field. */ +#define NCURSES_OPAQUE 0 +#endif + +#include +#include +#include + +#if defined STRICT_SYSV_CURSES +#define _m_STRICT_SYSV_CURSES TRUE +#else +#define _m_STRICT_SYSV_CURSES FALSE +#endif + +#if defined NCURSES_MOUSE_VERSION +#define _m_NCURSES_MOUSE_VERSION TRUE +#else +#define _m_NCURSES_MOUSE_VERSION FALSE +#endif + +#if defined __NetBSD__ +#define _m_NetBSD TRUE +#else +#define _m_NetBSD FALSE +#endif + +int _m_ispad(WINDOW *win) { + // may not have _flags (and possibly _ISPAD), + // but for now let's assume that always has it + return (win->_flags & _ISPAD); +} + +void _m_getsyx(int *yx) { + getsyx(yx[0], yx[1]); +} +""", libraries=['ncurses', 'panel']) + +if __name__ == '__main__': + ffi.compile() diff --git a/demo/_curses_setup.py b/demo/_curses_setup.py new file mode 100644 index 0000000..ec3d20e --- /dev/null +++ b/demo/_curses_setup.py @@ -0,0 +1,13 @@ +from setuptools import setup + +setup( + name="_curses", + version="0.1", + py_modules=["_curses"], + setup_requires=["cffi>=1.0.dev0"], + cffi_modules=[ + "_curses_build.py:ffi", + ], + install_requires=["cffi>=1.0.dev0"], # should maybe be "cffi-backend" only? + zip_safe=False, +) diff --git a/demo/api.py b/demo/api.py new file mode 100644 index 0000000..8cc6407 --- /dev/null +++ b/demo/api.py @@ -0,0 +1,62 @@ +import cffi +from cffi import FFI + +class PythonFFI(FFI): + + def __init__(self, backend=None): + FFI.__init__(self, backend=backend) + self._pyexports = {} + + def pyexport(self, signature): + tp = self._typeof(signature, consider_function_as_funcptr=True) + def decorator(func): + name = func.__name__ + if name in self._pyexports: + raise cffi.CDefError("duplicate pyexport'ed function %r" + % (name,)) + callback_var = self.getctype(tp, name) + self.cdef("%s;" % callback_var) + self._pyexports[name] = _PyExport(tp, func) + return decorator + + def verify(self, source='', **kwargs): + extras = [] + pyexports = sorted(self._pyexports.items()) + for name, export in pyexports: + callback_var = self.getctype(export.tp, name) + extras.append("%s;" % callback_var) + extras.append(source) + source = '\n'.join(extras) + lib = FFI.verify(self, source, **kwargs) + for name, export in pyexports: + cb = self.callback(export.tp, export.func) + export.cb = cb + setattr(lib, name, cb) + return lib + + +class _PyExport(object): + def __init__(self, tp, func): + self.tp = tp + self.func = func + + +if __name__ == '__main__': + ffi = PythonFFI() + + @ffi.pyexport("int(int)") + def add1(n): + print n + return n + 1 + + ffi.cdef(""" + int f(int); + """) + + lib = ffi.verify(""" + int f(int x) { + return add1(add1(x)); + } + """) + + assert lib.f(5) == 7 diff --git a/demo/bsdopendirtype.py b/demo/bsdopendirtype.py new file mode 100644 index 0000000..75a996a --- /dev/null +++ b/demo/bsdopendirtype.py @@ -0,0 +1,48 @@ +from _bsdopendirtype import ffi, lib + + +def _posix_error(): + raise OSError(ffi.errno, os.strerror(ffi.errno)) + +_dtype_to_smode = { + lib.DT_BLK: 0o060000, + lib.DT_CHR: 0o020000, + lib.DT_DIR: 0o040000, + lib.DT_FIFO: 0o010000, + lib.DT_LNK: 0o120000, + lib.DT_REG: 0o100000, + lib.DT_SOCK: 0o140000, +} + +def opendir(dir): + if len(dir) == 0: + dir = b'.' + dirname = dir + if not dirname.endswith(b'/'): + dirname += b'/' + dirp = lib.opendir(dir) + if dirp == ffi.NULL: + raise _posix_error() + try: + while True: + ffi.errno = 0 + dirent = lib.readdir(dirp) + if dirent == ffi.NULL: + if ffi.errno != 0: + raise _posix_error() + return + name = ffi.string(dirent.d_name) + if name == b'.' or name == b'..': + continue + name = dirname + name + try: + smode = _dtype_to_smode[dirent.d_type] + except KeyError: + smode = os.lstat(name).st_mode + yield name, smode + finally: + lib.closedir(dirp) + +if __name__ == '__main__': + for name, smode in opendir(b'/tmp'): + print(hex(smode), name) diff --git a/demo/bsdopendirtype_build.py b/demo/bsdopendirtype_build.py new file mode 100644 index 0000000..3c5bb0b --- /dev/null +++ b/demo/bsdopendirtype_build.py @@ -0,0 +1,23 @@ +from cffi import FFI + +ffibuilder = FFI() +ffibuilder.cdef(""" + typedef ... DIR; + struct dirent { + unsigned char d_type; /* type of file */ + char d_name[]; /* filename */ + ...; + }; + DIR *opendir(const char *name); + int closedir(DIR *dirp); + struct dirent *readdir(DIR *dirp); + static const int DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK; +""") + +ffibuilder.set_source("_bsdopendirtype", """ + #include + #include +""") + +if __name__ == '__main__': + ffibuilder.compile(verbose=True) diff --git a/demo/bsdopendirtype_setup.py b/demo/bsdopendirtype_setup.py new file mode 100644 index 0000000..30a6cfb --- /dev/null +++ b/demo/bsdopendirtype_setup.py @@ -0,0 +1,13 @@ +from setuptools import setup + +setup( + name="example", + version="0.1", + py_modules=["bsdopendirtype"], + setup_requires=["cffi>=1.0.dev0"], + cffi_modules=[ + "bsdopendirtype_build.py:ffibuilder", + ], + install_requires=["cffi>=1.0.dev0"], # should maybe be "cffi-backend" only? + zip_safe=False, +) diff --git a/demo/btrfs-snap.py b/demo/btrfs-snap.py new file mode 100644 index 0000000..fceeaa1 --- /dev/null +++ b/demo/btrfs-snap.py @@ -0,0 +1,52 @@ +""" +btrfs-snap.py: source target newname + +creates a exactly named snapshots and bails out if they exist +""" + +import argparse +import fcntl +import os +import sys + +import cffi + +ffi = cffi.FFI() + +ffi.cdef(""" + #define BTRFS_IOC_SNAP_CREATE_V2 ... + struct btrfs_ioctl_vol_args_v2 { + int64_t fd; + char name[]; + ...; + }; +""") + +ffi.set_source("_btrfs_cffi", "#include ") +ffi.compile() + +# ____________________________________________________________ + + +from _btrfs_cffi import ffi, lib + +parser = argparse.ArgumentParser(usage=__doc__.strip()) +parser.add_argument('source', help='source subvolume') +parser.add_argument('target', help='target directory') +parser.add_argument('newname', help='name of the new snapshot') +opts = parser.parse_args() + +source = os.open(opts.source, os.O_DIRECTORY) +target = os.open(opts.target, os.O_DIRECTORY) + + +args = ffi.new('struct btrfs_ioctl_vol_args_v2 *') +args.name = opts.newname +args.fd = source +args_buffer = ffi.buffer(args) +try: + fcntl.ioctl(target, lib.BTRFS_IOC_SNAP_CREATE_V2, args_buffer) +except IOError as e: + print e + sys.exit(1) + diff --git a/demo/cffi-cocoa.py b/demo/cffi-cocoa.py new file mode 100644 index 0000000..9e86d99 --- /dev/null +++ b/demo/cffi-cocoa.py @@ -0,0 +1,102 @@ +# Based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html +# by Juraj Sukop. This demo was eventually expanded into a more complete +# Cocoa library available at https://bitbucket.org/sukop/nspython . + +from cffi import FFI + +ffi = FFI() +ffi.cdef(''' + + typedef signed char BOOL; + + typedef long NSInteger; + typedef unsigned long NSUInteger; + typedef NSInteger NSApplicationActivationPolicy; + typedef NSUInteger NSBackingStoreType; + typedef NSUInteger NSStringEncoding; + + typedef double CGFloat; + struct CGPoint { + CGFloat x; + CGFloat y; + }; + typedef struct CGPoint CGPoint; + struct CGSize { + CGFloat width; + CGFloat height; + }; + typedef struct CGSize CGSize; + struct CGRect { + CGPoint origin; + CGSize size; + }; + typedef struct CGRect CGRect; + + typedef CGPoint NSPoint; + typedef CGSize NSSize; + typedef CGRect NSRect; + + typedef struct objc_class *Class; + typedef struct objc_object { + Class isa; + } *id; + typedef struct objc_selector *SEL; + + SEL sel_registerName(const char *str); + id objc_getClass(const char *name); + id objc_msgSend(id theReceiver, SEL theSelector, ...); + +''') + +objc = ffi.dlopen('objc') +appkit = ffi.dlopen('AppKit') + +nil = ffi.NULL +YES = ffi.cast('BOOL', 1) +NO = ffi.cast('BOOL', 0) + +NSASCIIStringEncoding = ffi.cast('NSStringEncoding', 1) +NSApplicationActivationPolicyRegular = ffi.cast('NSApplicationActivationPolicy', 0) +NSTitledWindowMask = ffi.cast('NSUInteger', 1) +NSBackingStoreBuffered = ffi.cast('NSBackingStoreType', 2) + +NSMakePoint = lambda x, y: ffi.new('NSPoint *', (x, y))[0] +NSMakeRect = lambda x, y, w, h: ffi.new('NSRect *', ((x, y), (w, h)))[0] + +get, send, sel = objc.objc_getClass, objc.objc_msgSend, objc.sel_registerName +at = lambda s: send( + get('NSString'), + sel('stringWithCString:encoding:'), + ffi.new('char[]', s), NSASCIIStringEncoding) + +send(get('NSAutoreleasePool'), sel('new')) +app = send(get('NSApplication'), sel('sharedApplication')) +send(app, sel('setActivationPolicy:'), NSApplicationActivationPolicyRegular) + +menubar = send(send(get('NSMenu'), sel('new')), sel('autorelease')) +appMenuItem = send(send(get('NSMenuItem'), sel('new')), sel('autorelease')) +send(menubar, sel('addItem:'), appMenuItem) +send(app, sel('setMainMenu:'), menubar) + +appMenu = send(send(get('NSMenu'), sel('new')), sel('autorelease')) +appName = send(send(get('NSProcessInfo'), sel('processInfo')), sel('processName')) +quitTitle = send(at('Quit '), sel('stringByAppendingString:'), appName) +quitMenuItem = send(send(send( + get('NSMenuItem'), sel('alloc')), + sel('initWithTitle:action:keyEquivalent:'), + quitTitle, sel('terminate:'), at('q')), + sel('autorelease')) +send(appMenu, sel('addItem:'), quitMenuItem) +send(appMenuItem, sel('setSubmenu:'), appMenu) + +window = send(send(send( + get('NSWindow'), sel('alloc')), + sel('initWithContentRect:styleMask:backing:defer:'), + NSMakeRect(0, 0, 200, 200), NSTitledWindowMask, NSBackingStoreBuffered, NO), + sel('autorelease')) +send(window, sel('cascadeTopLeftFromPoint:'), NSMakePoint(20, 20)) +send(window, sel('setTitle:'), appName) +send(window, sel('makeKeyAndOrderFront:'), nil) + +send(app, sel('activateIgnoringOtherApps:'), YES) +send(app, sel('run')) diff --git a/demo/embedding.py b/demo/embedding.py new file mode 100644 index 0000000..b15c050 --- /dev/null +++ b/demo/embedding.py @@ -0,0 +1,21 @@ +import cffi + +ffibuilder = cffi.FFI() + +ffibuilder.embedding_api(""" + int add(int, int); +""") + +ffibuilder.embedding_init_code(""" + from _embedding_cffi import ffi + print("preparing") # printed once + + @ffi.def_extern() + def add(x, y): + print("adding %d and %d" % (x, y)) + return x + y +""") + +ffibuilder.set_source("_embedding_cffi", "") + +ffibuilder.compile(verbose=True) diff --git a/demo/embedding_test.c b/demo/embedding_test.c new file mode 100644 index 0000000..ede8cb9 --- /dev/null +++ b/demo/embedding_test.c @@ -0,0 +1,43 @@ +/* There are two options: + + =====1===== + + Link this program with _embedding_test.so. + E.g. with gcc: + + gcc -o embedding_test embedding_test.c _embedding_cffi*.so + + You must then run the executable with the right command + (LD_LIBRARY_PATH on Linux), otherwise it won't find the + _embedding_cffi*.so: + + LD_LIBRARY_PATH=. ./embedding_test + + There are platform-specific options to gcc to avoid needing + that, too. Linux: + + gcc -o embedding_test embedding_test.c _embedding_cffi*.so \ + -Wl,-rpath=\$ORIGIN/ + + =====2===== + + Compile and link the _embedding_test.c source code together with + this example (e.g. with PyPy): + + gcc -o embedding_test embedding_test.c _embedding_cffi.c \ + -I/opt/pypy/include -pthread -lpypy-c +*/ + +#include + +extern int add(int x, int y); + + +int main(void) +{ + int res = add(40, 2); + printf("result: %d\n", res); + res = add(100, -5); + printf("result: %d\n", res); + return 0; +} diff --git a/demo/extern_python.py b/demo/extern_python.py new file mode 100644 index 0000000..f315cc5 --- /dev/null +++ b/demo/extern_python.py @@ -0,0 +1,26 @@ +import cffi + +ffi = cffi.FFI() + +ffi.cdef("""int my_algo(int); extern "Python" int f(int);""") + +ffi.set_source("_extern_python_cffi", """ + static int f(int); + static int my_algo(int n) { + int i, sum = 0; + for (i = 0; i < n; i++) + sum += f(i); + return sum; + } +""") + +ffi.compile() + + +from _extern_python_cffi import ffi, lib + +@ffi.def_extern() +def f(n): + return n * n + +assert lib.my_algo(10) == 0+1+4+9+16+25+36+49+64+81 diff --git a/demo/extern_python_varargs.py b/demo/extern_python_varargs.py new file mode 100644 index 0000000..ee78079 --- /dev/null +++ b/demo/extern_python_varargs.py @@ -0,0 +1,61 @@ +import cffi + +ffi = cffi.FFI() + +ffi.cdef(""" + int my_algo(int); + typedef ... va_list; + extern "Python" int f(int, va_list *); + + int fetch_int(va_list *); + double fetch_double(va_list *); + void *fetch_ptr(va_list *); +""") + +ffi.set_source("_extern_python_cffi", """ + #include + + static int f(int, va_list *); + + static int f1(int n, ...) + { + va_list ap; + va_start(ap, n); + int res = f(n, &ap); + va_end(ap); + return res; + } + + static int fetch_int(va_list *va) { return va_arg((*va), int); } + static double fetch_double(va_list *va) { return va_arg((*va), double); } + static void * fetch_ptr(va_list *va) { return va_arg((*va), void *); } + + static int my_algo(int n) { + return f1(3, n, n+1, n+2) + f1(1, &n) + f1(2, 12.3, 45.6); + } +""") + +ffi.compile() + + +from _extern_python_cffi import ffi, lib + +@ffi.def_extern() +def f(n, va): + if n == 3: + x = lib.fetch_int(va) + y = lib.fetch_int(va) + z = lib.fetch_int(va) + print (x, y, z) + elif n == 1: + ptr = lib.fetch_ptr(va) + print 'ptr to:', ffi.cast("int *", ptr)[0] + elif n == 2: + x = lib.fetch_double(va) + y = lib.fetch_double(va) + print (x, y) + else: + raise AssertionError(n) + return 14 + +print lib.my_algo(10) diff --git a/demo/fastcsv.py b/demo/fastcsv.py new file mode 100644 index 0000000..6b8d0b4 --- /dev/null +++ b/demo/fastcsv.py @@ -0,0 +1,266 @@ +import csv +import cffi + +# IN-PROGRESS. See the demo at the end of the file + + +def _make_ffi_from_dialect(dialect_name): + dialect = csv.get_dialect(dialect_name) + + ffi = cffi.FFI() + + ffi.cdef(""" + long parse_line(char *rawline, long inputlength); + """) + + d = {'quotechar': ord(dialect.quotechar), + 'quoting': int(dialect.quoting), + 'skipinitialspace': int(dialect.skipinitialspace), + 'delimiter': ord(dialect.delimiter), + 'doublequote': int(dialect.doublequote), + 'strict': int(dialect.strict), + } + if dialect.escapechar is not None: + d['is_escape_char'] = '== %d' % ord(dialect.escapechar) + else: + d['is_escape_char'] = '&& 0' + + ffi.set_source('_fastcsv_' + dialect_name, r''' + + typedef enum { + START_RECORD, START_FIELD, ESCAPED_CHAR, IN_FIELD, + IN_QUOTED_FIELD, ESCAPE_IN_QUOTED_FIELD, QUOTE_IN_QUOTED_FIELD, + EAT_CRNL + } ParserState; + + typedef enum { + QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE + } QuoteStyle; + + typedef struct { + ParserState state; /* current CSV parse state */ + char *field; /* build current field in here */ + int field_size; /* size of allocated buffer */ + int field_len; /* length of current field */ + int numeric_field; /* treat field as numeric */ + } ReaderObj; + + static void + parse_add_char(ReaderObj *self, char c) + { + *self->field++ = c; + } + + static void + parse_save_field(ReaderObj *self) + { + *self->field++ = 0; + } + + static int + parse_process_char(ReaderObj *self, char c) + { + switch (self->state) { + case START_RECORD: + /* start of record */ + if (c == '\0') + /* empty line - return [] */ + break; + else if (c == '\n' || c == '\r') { + self->state = EAT_CRNL; + break; + } + /* normal character - handle as START_FIELD */ + self->state = START_FIELD; + /* fallthru */ + case START_FIELD: + /* expecting field */ + if (c == '\n' || c == '\r' || c == '\0') { + /* save empty field - return [fields] */ + parse_save_field(self); + self->state = (c == '\0' ? START_RECORD : EAT_CRNL); + } + else if (c == %(quotechar)d && + %(quoting)d != QUOTE_NONE) { + /* start quoted field */ + self->state = IN_QUOTED_FIELD; + } + else if (c %(is_escape_char)s) { + /* possible escaped character */ + self->state = ESCAPED_CHAR; + } + else if (c == ' ' && %(skipinitialspace)d) + /* ignore space at start of field */ + ; + else if (c == %(delimiter)d) { + /* save empty field */ + parse_save_field(self); + } + else { + /* begin new unquoted field */ + if (%(quoting)d == QUOTE_NONNUMERIC) + self->numeric_field = 1; + parse_add_char(self, c); + self->state = IN_FIELD; + } + break; + + case ESCAPED_CHAR: + if (c == '\0') + c = '\n'; + parse_add_char(self, c); + self->state = IN_FIELD; + break; + + case IN_FIELD: + /* in unquoted field */ + if (c == '\n' || c == '\r' || c == '\0') { + /* end of line - return [fields] */ + parse_save_field(self); + self->state = (c == '\0' ? START_RECORD : EAT_CRNL); + } + else if (c %(is_escape_char)s) { + /* possible escaped character */ + self->state = ESCAPED_CHAR; + } + else if (c == %(delimiter)d) { + /* save field - wait for new field */ + parse_save_field(self); + self->state = START_FIELD; + } + else { + /* normal character - save in field */ + parse_add_char(self, c); + } + break; + + case IN_QUOTED_FIELD: + /* in quoted field */ + if (c == '\0') + ; + else if (c %(is_escape_char)s) { + /* Possible escape character */ + self->state = ESCAPE_IN_QUOTED_FIELD; + } + else if (c == %(quotechar)d && + %(quoting)d != QUOTE_NONE) { + if (%(doublequote)d) { + /* doublequote; " represented by "" */ + self->state = QUOTE_IN_QUOTED_FIELD; + } + else { + /* end of quote part of field */ + self->state = IN_FIELD; + } + } + else { + /* normal character - save in field */ + parse_add_char(self, c); + } + break; + + case ESCAPE_IN_QUOTED_FIELD: + if (c == '\0') + c = '\n'; + parse_add_char(self, c); + self->state = IN_QUOTED_FIELD; + break; + + case QUOTE_IN_QUOTED_FIELD: + /* doublequote - seen a quote in an quoted field */ + if (%(quoting)d != QUOTE_NONE && + c == %(quotechar)d) { + /* save "" as " */ + parse_add_char(self, c); + self->state = IN_QUOTED_FIELD; + } + else if (c == %(delimiter)d) { + /* save field - wait for new field */ + parse_save_field(self); + self->state = START_FIELD; + } + else if (c == '\n' || c == '\r' || c == '\0') { + /* end of line - return [fields] */ + parse_save_field(self); + self->state = (c == '\0' ? START_RECORD : EAT_CRNL); + } + else if (!%(strict)d) { + parse_add_char(self, c); + self->state = IN_FIELD; + } + else { + /* illegal */ + /*PyErr_Format(error_obj, "'%%c' expected after '%%c'", + dialect->delimiter, + dialect->quotechar);*/ + return -1; + } + break; + + case EAT_CRNL: + if (c == '\n' || c == '\r') + ; + else if (c == '\0') + self->state = START_RECORD; + else { + /*PyErr_Format(error_obj, "new-line character seen in unquoted field - do you need to open the file in universal-newline mode?");*/ + return -1; + } + break; + + } + return 0; + } + + static void + parse_reset(ReaderObj *self, char *rawline) + { + self->field = rawline; + self->state = START_RECORD; + self->numeric_field = 0; + } + + long parse_line(char *rawline, long inputlength) + { + char *p; + ReaderObj reader; + parse_reset(&reader, rawline); + + for (p=rawline; inputlength > 0; inputlength--, p++) { + if (parse_process_char(&reader, *p) < 0) + return -1; + } + if (parse_process_char(&reader, 0) < 0) + return -1; + return reader.field - rawline - 1; + } + ''' % d) + + ffi.compile() + + +def fastcsv_reader(f, dialect_name): + try: + module = __import__('_fastcsv_' + dialect_name) + except ImportError: + _make_ffi_from_dialect(dialect_name) + module = __import__('_fastcsv_' + dialect_name) + ffi, lib = module.ffi, module.lib + # + linelen = -1 + for line in f: + if linelen <= len(line): + linelen = 2 * len(line) + rawline = ffi.new("char[]", linelen) + ffi.buffer(rawline, len(line))[:] = line + n = lib.parse_line(rawline, len(line)) + assert n >= 0 + yield ffi.buffer(rawline, n)[:].split('\x00') + + +if __name__ == '__main__': + csv.register_dialect('unixpwd', delimiter=':', quoting=csv.QUOTE_NONE) + with open('/etc/passwd', 'rb') as f: + reader = fastcsv_reader(f, 'unixpwd') + for row in reader: + print row diff --git a/demo/gmp.py b/demo/gmp.py new file mode 100644 index 0000000..44f233c --- /dev/null +++ b/demo/gmp.py @@ -0,0 +1,33 @@ +import sys +# +# This is only a demo based on the GMP library. +# There is a rather more complete (but perhaps outdated) version available at: +# http://bazaar.launchpad.net/~tolot-solar-empire/+junk/gmpy_cffi/files +# + +try: + from _gmp_cffi import ffi, lib +except ImportError: + print 'run gmp_build first, then make sure the shared object is on sys.path' + sys.exit(1) + +# ffi "knows" about the declared variables and functions from the +# cdef parts of the module created from gmp_build +# lib "knows" how to call the functions from the set_source parts +# of the module. + +# ____________________________________________________________ + +a = ffi.new("mpz_t") +b = ffi.new("mpz_t") + +if len(sys.argv) < 3: + print 'call as %s bigint1, bigint2' % sys.argv[0] + sys.exit(2) + +lib.mpz_init_set_str(a, sys.argv[1], 10) # Assume decimal integers +lib.mpz_init_set_str(b, sys.argv[2], 10) # Assume decimal integers +lib.mpz_add(a, a, b) # a=a+b + +s = lib.mpz_get_str(ffi.NULL, 10, a) +print ffi.string(s) diff --git a/demo/gmp_build.py b/demo/gmp_build.py new file mode 100644 index 0000000..e1a6000 --- /dev/null +++ b/demo/gmp_build.py @@ -0,0 +1,26 @@ +import cffi + +# +# This is only a demo based on the GMP library. +# There is a rather more complete (but perhaps outdated) version available at: +# http://bazaar.launchpad.net/~tolot-solar-empire/+junk/gmpy_cffi/files +# + +ffibuilder = cffi.FFI() + +ffibuilder.cdef(""" + + typedef struct { ...; } MP_INT; + typedef MP_INT mpz_t[1]; + + int mpz_init_set_str (MP_INT *dest_integer, char *src_cstring, int base); + void mpz_add (MP_INT *sum, MP_INT *addend1, MP_INT *addend2); + char * mpz_get_str (char *string, int base, MP_INT *integer); + +""") + +ffibuilder.set_source('_gmp_cffi', "#include ", + libraries=['gmp', 'm']) + +if __name__ == '__main__': + ffibuilder.compile(verbose=True) diff --git a/demo/manual.c b/demo/manual.c new file mode 100644 index 0000000..5b360e8 --- /dev/null +++ b/demo/manual.c @@ -0,0 +1,166 @@ +#include "_cffi_include.h" + + +#define AA (42) +#define BB (&bb) +static int bb = 16261; + +int foo42(int a, int *b) +{ + return a - *b; +} + +int foo64(int a) +{ + return ~a; +} + +struct foo_s { + int a; +}; + +/************************************************************/ + +static void *_cffi_types[] = { + _CFFI_OP(_CFFI_OP_FUNCTION, 1), + _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_INT), + _CFFI_OP(_CFFI_OP_POINTER, 1), + _CFFI_OP(_CFFI_OP_FUNCTION_END, 0), + _CFFI_OP(_CFFI_OP_FUNCTION, 1), + _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_INT), + _CFFI_OP(_CFFI_OP_FUNCTION_END, 0), + _CFFI_OP(_CFFI_OP_STRUCT_UNION, 0), +}; + +#ifndef PYPY_VERSION +static PyObject * +_cffi_f_foo42(PyObject *self, PyObject *args) +{ + int x0; + int * x1; + Py_ssize_t datasize; + int result; + PyObject *arg0; + PyObject *arg1; + + if (!PyArg_ParseTuple(args, "OO:foo42", &arg0, &arg1)) + return NULL; + + x0 = _cffi_to_c_int(arg0, int); + if (x0 == (int)-1 && PyErr_Occurred()) + return NULL; + + datasize = _cffi_prepare_pointer_call_argument( + _cffi_types[1], arg1, (char **)&x1); + if (datasize != 0) { + if (datasize < 0) + return NULL; + x1 = alloca(datasize); + memset((void *)x1, 0, datasize); + if (_cffi_convert_array_from_object((char *)x1, _cffi_types[1], arg1) < 0) + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + _cffi_restore_errno(); + { result = foo42(x0, x1); } + _cffi_save_errno(); + Py_END_ALLOW_THREADS + + return _cffi_from_c_int(result, int); +} +#else +static int _cffi_f_foo42(int x0, int *x1) +{ + return foo42(x0, x1); +} +#endif + +#ifndef PYPY_VERSION +static PyObject * +_cffi_f_foo64(PyObject *self, PyObject *arg0) +{ + int x0; + int result; + + x0 = _cffi_to_c_int(arg0, int); + if (x0 == (int)-1 && PyErr_Occurred()) + return NULL; + + Py_BEGIN_ALLOW_THREADS + _cffi_restore_errno(); + { result = foo64(x0); } + _cffi_save_errno(); + Py_END_ALLOW_THREADS + + return _cffi_from_c_int(result, int); +} +#else +static int _cffi_f_foo64(int x0) +{ + return foo64(x0); +} +#endif + +static int _cffi_const_AA(unsigned long long *output) +{ + *output = (unsigned long long)((AA) << 0); // integer + return (AA) <= 0; +} + +static void _cffi_const_BB(char *output) +{ + *(int **)output = BB; +} + +static const struct _cffi_global_s _cffi_globals[] = { + { "AA", &_cffi_const_AA, _CFFI_OP(_CFFI_OP_CONSTANT_INT, 0) }, + { "BB", &_cffi_const_BB, _CFFI_OP(_CFFI_OP_CONSTANT, 2) }, + { "bb", &bb, _CFFI_OP(_CFFI_OP_GLOBAL_VAR, 1) }, + { "foo42", &_cffi_f_foo42, _CFFI_OP(_CFFI_OP_CPYTHON_BLTN_V, 0) }, + { "foo64", &_cffi_f_foo64, _CFFI_OP(_CFFI_OP_CPYTHON_BLTN_O, 4) }, +}; + +struct _cffi_align_foo_s { char x; struct foo_s y; }; + +static const struct _cffi_struct_union_s _cffi_struct_unions[] = { + { "foo_s", 7, 0, + sizeof(struct foo_s), + offsetof(struct _cffi_align_foo_s, y), + 1, 0 }, +}; + +static const struct _cffi_field_s _cffi_fields[] = { + { "a", offsetof(struct foo_s, a), sizeof(((struct foo_s *)0)->a), + _CFFI_OP(_CFFI_OP_NOOP, 1) }, +}; + +static const struct _cffi_type_context_s _cffi_type_context = { + _cffi_types, + _cffi_globals, + _cffi_fields, + _cffi_struct_unions, + NULL, + NULL, + 5, /* num_globals */ + 1, /* num_struct_unions */ + 0, + 0, + NULL, + 8, /* num_types */ +}; + +#ifndef PYPY_VERSION +PyMODINIT_FUNC +initmanual(void) +{ + _cffi_init("manual", 0x2601, &_cffi_type_context); +} +#else +PyMODINIT_FUNC +_cffi_pypyinit_manual(const void *p[]) +{ + p[0] = (const void *)0x2601; + p[1] = &_cffi_type_context; +} +#endif diff --git a/demo/manual2.py b/demo/manual2.py new file mode 100644 index 0000000..2986244 --- /dev/null +++ b/demo/manual2.py @@ -0,0 +1,34 @@ +import _cffi_backend + +ffi = _cffi_backend.FFI(b"manual2", + _version = 0x2601, + _types = b'\x00\x00\x01\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x00\x0B\x00\x00\x01\x03', + _globals = (b'\xff\xff\xff\x0bAA',0,b'\xff\xff\xff\x0bBB',-1,b'\xff\xff\xff\x0bCC',2,b'\xff\xff\xff\x1fFOO',0x9999999999999999,b'\x00\x00\x00#close',0,b'\x00\x00\x05#stdout',0), + _struct_unions = ((b'\x00\x00\x00\x03\x00\x00\x00\x00point_s',b'\x00\x00\x01\x11\xff\xff\xff\xffx',b'\x00\x00\x01\x11\xff\xff\xff\xffy'),), + _enums = (b'\x00\x00\x00\x04\x00\x00\x00\x07myenum_e\x00AA,BB,CC',), + _typenames = (b'\x00\x00\x00\x01myint_t',), +) + + + +# trying it out +lib = ffi.dlopen(None) +assert lib.AA == 0 +assert lib.BB == -1 +assert lib.FOO == 0x9999999999999999 +x = lib.close(-42) +assert x == -1 + +print lib.stdout + +print ffi.new("struct point_s *") +print ffi.offsetof("struct point_s", "x") +print ffi.offsetof("struct point_s", "y") +print ffi.new("struct point_s[CC]") +assert ffi.sizeof("struct point_s[CC]") == 2 * ffi.sizeof("struct point_s") + +print ffi.cast("enum myenum_e", 2) +print ffi.cast("myint_t", -2) +assert ffi.typeof("myint_t") == ffi.typeof("int") + +del ffi, lib diff --git a/demo/pwuid.py b/demo/pwuid.py new file mode 100644 index 0000000..dda9299 --- /dev/null +++ b/demo/pwuid.py @@ -0,0 +1,7 @@ +import sys, os + +# run pwuid_build first, then make sure the shared object is on sys.path +from _pwuid_cffi import ffi, lib + + +print ffi.string(lib.getpwuid(0).pw_name) diff --git a/demo/pwuid_build.py b/demo/pwuid_build.py new file mode 100644 index 0000000..7ef0d76 --- /dev/null +++ b/demo/pwuid_build.py @@ -0,0 +1,18 @@ +from cffi import FFI +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") + +ffi.set_source('_pwuid_cffi', """ // passed to the real C compiler +#include +#include +""") + + +if __name__ == '__main__': + ffi.compile() diff --git a/demo/py.cleanup b/demo/py.cleanup new file mode 100755 index 0000000..512389f --- /dev/null +++ b/demo/py.cleanup @@ -0,0 +1,31 @@ +#! /usr/bin/env python +import sys, os, stat +from bsdopendirtype import opendir + +def clean(path): + global count + try: + content = opendir(path) + except OSError: + print >> sys.stderr, "skipping", path + return + for filename, smode in content: + if stat.S_ISDIR(smode): + clean(filename) + if filename.endswith('/__pycache__'): + try: + os.rmdir(filename) + except OSError: + pass + elif (filename.endswith('.pyc') or filename.endswith('.pyo') or + filename.endswith('.pyc~') or filename.endswith('.pyo~')): + os.unlink(filename) + count += 1 + +count = 0 + +for arg in sys.argv[1:] or ['.']: + print "cleaning path", arg, "of .pyc/.pyo/__pycache__ files" + clean(arg) + +print "%d files removed" % (count,) diff --git a/demo/pyobj.py b/demo/pyobj.py new file mode 100644 index 0000000..b40343a --- /dev/null +++ b/demo/pyobj.py @@ -0,0 +1,124 @@ + +referents = [] # list "object descriptor -> python object" +freelist = None + +def store(x): + "Store the object 'x' and returns a new object descriptor for it." + global freelist + p = freelist + if p is None: + p = len(referents) + referents.append(x) + else: + freelist = referents[p] + referents[p] = x + return p + +def discard(p): + """Discard (i.e. close) the object descriptor 'p'. + Return the original object that was attached to 'p'.""" + global freelist + x = referents[p] + referents[p] = freelist + freelist = p + return x + +class Ref(object): + """For use in 'with Ref(x) as ob': open an object descriptor + and returns it in 'ob', and close it automatically when the + 'with' statement finishes.""" + def __init__(self, x): + self.x = x + def __enter__(self): + self.p = p = store(self.x) + return p + def __exit__(self, *args): + discard(self.p) + +def count_pyobj_alive(): + result = len(referents) + p = freelist + while p is not None: + assert result > 0 + result -= 1 + p = referents[p] + return result + +# ------------------------------------------------------------ + +if __name__ == '__main__': + import api + + ffi = api.PythonFFI() + + ffi.cdef(""" + typedef int pyobj_t; + int sum_integers(pyobj_t p_list); + pyobj_t sum_objects(pyobj_t p_list, pyobj_t p_initial); + """) + + @ffi.pyexport("int(pyobj_t)") + def length(p_list): + list = referents[p_list] + return len(list) + + @ffi.pyexport("int(pyobj_t, int)") + def getitem(p_list, index): + list = referents[p_list] + return list[index] + + @ffi.pyexport("pyobj_t(pyobj_t)") + def pyobj_dup(p): + return store(referents[p]) + + @ffi.pyexport("void(pyobj_t)") + def pyobj_close(p): + discard(p) + + @ffi.pyexport("pyobj_t(pyobj_t, int)") + def pyobj_getitem(p_list, index): + list = referents[p_list] + return store(list[index]) + + @ffi.pyexport("pyobj_t(pyobj_t, pyobj_t)") + def pyobj_add(p1, p2): + return store(referents[p1] + referents[p2]) + + lib = ffi.verify(""" + typedef int pyobj_t; /* an "object descriptor" number */ + + int sum_integers(pyobj_t p_list) { + /* this a demo function written in C, using the API + defined above: length() and getitem(). */ + int i, result = 0; + int count = length(p_list); + for (i=0; i +#include +#include +""") + +if __name__ == '__main__': + ffi.compile() diff --git a/demo/readdir2_setup.py b/demo/readdir2_setup.py new file mode 100644 index 0000000..bd8c19f --- /dev/null +++ b/demo/readdir2_setup.py @@ -0,0 +1,9 @@ +from distutils.core import setup +import readdir2_build + +setup( + name="readdir2", + version="0.1", + py_modules=["readdir2"], + ext_modules=[readdir2_build.ffi.distutils_extension('build')], +) diff --git a/demo/readdir_build.py b/demo/readdir_build.py new file mode 100644 index 0000000..f97f404 --- /dev/null +++ b/demo/readdir_build.py @@ -0,0 +1,33 @@ +import sys +from cffi import FFI + +if not sys.platform.startswith('linux'): + raise Exception("Linux-only demo") + + +ffi = FFI() +ffi.cdef(""" + + typedef void DIR; + typedef long ino_t; + typedef long off_t; + + struct dirent { + ino_t d_ino; /* inode number */ + off_t d_off; /* offset to the next dirent */ + unsigned short d_reclen; /* length of this record */ + unsigned char d_type; /* type of file; not supported + by all file system types */ + char d_name[256]; /* filename */ + }; + + int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); + int openat(int dirfd, const char *pathname, int flags); + DIR *fdopendir(int fd); + int closedir(DIR *dirp); + +""") +ffi.set_source("_readdir", None) + +if __name__ == '__main__': + ffi.compile() diff --git a/demo/readdir_ctypes.py b/demo/readdir_ctypes.py new file mode 100644 index 0000000..4fd1d17 --- /dev/null +++ b/demo/readdir_ctypes.py @@ -0,0 +1,69 @@ +# A Linux-only demo +# +# For comparison purposes, this is a ctypes version of readdir.py. +import sys +import ctypes + +if not sys.platform.startswith('linux'): + raise Exception("Linux-only demo") + + +DIR_p = ctypes.c_void_p +ino_t = ctypes.c_long +off_t = ctypes.c_long + +class DIRENT(ctypes.Structure): + _fields_ = [ + ('d_ino', ino_t), # inode number + ('d_off', off_t), # offset to the next dirent + ('d_reclen', ctypes.c_ushort), # length of this record + ('d_type', ctypes.c_ubyte), # type of file; not supported + # by all file system types + ('d_name', ctypes.c_char * 256), # filename + ] +DIRENT_p = ctypes.POINTER(DIRENT) +DIRENT_pp = ctypes.POINTER(DIRENT_p) + +C = ctypes.CDLL(None) + +readdir_r = C.readdir_r +readdir_r.argtypes = [DIR_p, DIRENT_p, DIRENT_pp] +readdir_r.restype = ctypes.c_int + +openat = C.openat +openat.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int] +openat.restype = ctypes.c_int + +fdopendir = C.fdopendir +fdopendir.argtypes = [ctypes.c_int] +fdopendir.restype = DIR_p + +closedir = C.closedir +closedir.argtypes = [DIR_p] +closedir.restype = ctypes.c_int + + +def walk(basefd, path): + print '{', path + dirfd = openat(basefd, path, 0) + if dirfd < 0: + # error in openat() + return + dir = fdopendir(dirfd) + dirent = DIRENT() + result = DIRENT_p() + while True: + if readdir_r(dir, dirent, result): + # error in readdir_r() + break + if not result: + break + name = dirent.d_name + print '%3d %s' % (dirent.d_type, name) + if dirent.d_type == 4 and name != '.' and name != '..': + walk(dirfd, name) + closedir(dir) + print '}' + + +walk(-1, "/tmp") diff --git a/demo/readdir_setup.py b/demo/readdir_setup.py new file mode 100644 index 0000000..c8abdcb --- /dev/null +++ b/demo/readdir_setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup( + name="example", + version="0.1", + py_modules=["readdir"], + setup_requires=["cffi>=1.0.dev0"], + cffi_modules=["readdir_build.py:ffi"], + install_requires=["cffi>=1.0.dev0"], + zip_safe=False, +) diff --git a/demo/recopendirtype.py b/demo/recopendirtype.py new file mode 100644 index 0000000..768318b --- /dev/null +++ b/demo/recopendirtype.py @@ -0,0 +1,50 @@ +from _recopendirtype import ffi, lib + + +def _posix_error(): + raise OSError(ffi.errno, os.strerror(ffi.errno)) + +_dtype_to_smode = { + lib.DT_BLK: 0o060000, + lib.DT_CHR: 0o020000, + lib.DT_DIR: 0o040000, + lib.DT_FIFO: 0o010000, + lib.DT_LNK: 0o120000, + lib.DT_REG: 0o100000, + lib.DT_SOCK: 0o140000, +} + +def opendir(dir): + if len(dir) == 0: + dir = b'.' + dirname = dir + if not dirname.endswith(b'/'): + dirname += b'/' + dirp = lib.opendir(dir) + if dirp == ffi.NULL: + raise _posix_error() + dirent = ffi.new("struct dirent *") + result = ffi.new("struct dirent **") + try: + while True: + ffi.errno = 0 + err = lib.readdir_r(dirp, dirent, result) + if err: # really got an error + raise OSError(err, os.strerror(err)) + if result[0] == ffi.NULL: + return # + name = ffi.string(dirent.d_name) + if name == b'.' or name == b'..': + continue + name = dirname + name + try: + smode = _dtype_to_smode[dirent.d_type] + except KeyError: + smode = os.lstat(name).st_mode + yield name, smode + finally: + lib.closedir(dirp) + +if __name__ == '__main__': + for name, smode in opendir(b'/tmp'): + print(hex(smode), name) diff --git a/demo/recopendirtype_build.py b/demo/recopendirtype_build.py new file mode 100644 index 0000000..fa62a05 --- /dev/null +++ b/demo/recopendirtype_build.py @@ -0,0 +1,19 @@ +from cffi import FFI +import bsdopendirtype_build + +ffi = FFI() + +# ========== This is a demo of ffi.include() ========== +ffi.include(bsdopendirtype_build.ffi) + +ffi.cdef(""" + int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); +""") + +ffi.set_source("_recopendirtype", """ + #include + #include +""") + +if __name__ == '__main__': + ffi.compile() diff --git a/demo/setup_manual.py b/demo/setup_manual.py new file mode 100644 index 0000000..2569bb4 --- /dev/null +++ b/demo/setup_manual.py @@ -0,0 +1,5 @@ +from distutils.core import setup +from distutils.extension import Extension +setup(name='manual', + ext_modules=[Extension(name='manual', + sources=['manual.c'])]) diff --git a/demo/winclipboard.py b/demo/winclipboard.py new file mode 100644 index 0000000..5278cd0 --- /dev/null +++ b/demo/winclipboard.py @@ -0,0 +1,40 @@ +__author__ = "Israel Fruchter " + +import sys, os + +if not sys.platform == 'win32': + raise Exception("Windows-only demo") + +try: + from _winclipboard_cffi import ffi, lib +except ImportError: + print 'run winclipboard_build first, then make sure the shared object is on sys.path' + sys.exit(1) + +# ffi "knows" about the declared variables and functions from the +# cdef parts of the module _winclipboard_cffi created, +# lib "knows" how to call the functions from the set_source parts +# of the module. + +def CopyToClipboard(string): + ''' + use win32 api to copy `string` to the clipboard + ''' + hWnd = lib.GetConsoleWindow() + + if lib.OpenClipboard(hWnd): + cstring = ffi.new("char[]", string) + size = ffi.sizeof(cstring) + + # make it a moveable memory for other processes + hGlobal = lib.GlobalAlloc(lib.GMEM_MOVEABLE, size) + buffer = lib.GlobalLock(hGlobal) + lib.memcpy(buffer, cstring, size) + lib.GlobalUnlock(hGlobal) + + res = lib.EmptyClipboard() + res = lib.SetClipboardData(lib.CF_TEXT, buffer) + + lib.CloseClipboard() + +CopyToClipboard("hello world from cffi") diff --git a/demo/winclipboard_build.py b/demo/winclipboard_build.py new file mode 100644 index 0000000..1a510eb --- /dev/null +++ b/demo/winclipboard_build.py @@ -0,0 +1,36 @@ +from cffi import FFI + +ffi = FFI() +ffi.cdef(''' + typedef void * HANDLE; + typedef HANDLE HWND; + typedef int BOOL; + typedef unsigned int UINT; + typedef int SIZE_T; + typedef char * LPTSTR; + typedef HANDLE HGLOBAL; + typedef HANDLE LPVOID; + + HWND GetConsoleWindow(void); + + LPVOID GlobalLock( HGLOBAL hMem ); + BOOL GlobalUnlock( HGLOBAL hMem ); + HGLOBAL GlobalAlloc(UINT uFlags, SIZE_T dwBytes); + + BOOL OpenClipboard(HWND hWndNewOwner); + BOOL CloseClipboard(void); + BOOL EmptyClipboard(void); + HANDLE SetClipboardData(UINT uFormat, HANDLE hMem); + + #define CF_TEXT ... + #define GMEM_MOVEABLE ... + + void * memcpy(void * s1, void * s2, int n); + ''') + +ffi.set_source('_winclipboard_cffi', ''' + #include +''', libraries=["user32"]) + +if __name__ == '__main__': + ffi.compile() diff --git a/demo/xclient.py b/demo/xclient.py new file mode 100644 index 0000000..e4b3dd2 --- /dev/null +++ b/demo/xclient.py @@ -0,0 +1,27 @@ +import sys, os + +# run xclient_build first, then make sure the shared object is on sys.path +from _xclient_cffi import ffi, lib + + +# ffi "knows" about the declared variables and functions from the +# cdef parts of the module xclient_build created, +# lib "knows" how to call the functions from the set_source parts +# of the module. + + +class XError(Exception): + pass + +def main(): + display = lib.XOpenDisplay(ffi.NULL) + if display == ffi.NULL: + raise XError("cannot open display") + w = lib.XCreateSimpleWindow(display, lib.DefaultRootWindow(display), + 10, 10, 500, 350, 0, 0, 0) + lib.XMapRaised(display, w) + event = ffi.new("XEvent *") + lib.XNextEvent(display, event) + +if __name__ == '__main__': + main() diff --git a/demo/xclient_build.py b/demo/xclient_build.py new file mode 100644 index 0000000..d6ce9da --- /dev/null +++ b/demo/xclient_build.py @@ -0,0 +1,25 @@ +from cffi import FFI +ffi = FFI() +ffi.cdef(""" + +typedef ... Display; +typedef struct { ...; } Window; + +typedef struct { int type; ...; } XEvent; + +Display *XOpenDisplay(char *display_name); +Window DefaultRootWindow(Display *display); +int XMapRaised(Display *display, Window w); +Window XCreateSimpleWindow(Display *display, Window parent, int x, int y, + unsigned int width, unsigned int height, + unsigned int border_width, unsigned long border, + unsigned long background); +int XNextEvent(Display *display, XEvent *event_return); +""") + +ffi.set_source('_xclient_cffi', """ + #include +""", libraries=['X11']) + +if __name__ == '__main__': + ffi.compile(verbose=True) diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..285361c --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CFFI.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CFFI.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..5daa6b5 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\CFFI.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\CFFI.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/doc/misc/design.rst b/doc/misc/design.rst new file mode 100644 index 0000000..390049c --- /dev/null +++ b/doc/misc/design.rst @@ -0,0 +1,51 @@ +================ +Design decisions +================ + +* Generally follow LuaJIT's ffi: http://luajit.org/ext_ffi.html + +* Be explicit: almost no automatic conversions. Here is the set + of automatic conversions: the various C integer types are + automatically wrapped and unwrapped to regular applevel integers. The + type ``char`` might correspond to single-character strings instead; + for integer correspondance you would use ``signed char`` or ``unsigned + char``. We might also decide that ``const char *`` automatically maps + to strings; for cases where you don't want that, use ``char *``. + +* Integers are not automatically converted when passed as vararg + arguments. You have to use explicitly ``ffi.new("int", 42)`` or + ``ffi.new("long", 42)`` to resolve the ambiguity. Floats would be + fine (varargs in C can only accept ``double``, not ``float``), but + there is again ambiguity between characters and strings. Even with + floats the result is a bit strange because passing a float works + but passing an integer not. I would fix this once and for all by + saying that varargs must *always* be a cdata (from ``ffi.new()``). + The possibly acceptable exception would be None (for ``NULL``). + +* The internal class ``blob`` is used for raw-malloced data. You only + get a class that has internally a ``blob`` instance (or maybe is a + subclass of ``blob``) by calling ``ffi.new(struct-or-array-type)``. + The other cases, namely the cases where the type is a pointer or a + primitive, don't need a blob because it's not possible to take their + raw address. + +* It would be possible to add a debug mode: when we cast ``struct foo`` + to ``struct foo *`` or store it in some other struct, then we would + additionally record a weakref to the original ``struct foo`` blob. + If later we try to access the ``struct foo *`` but the weakref shows + that the blob was freed, we complain. This is a difference with + ctypes, which in these cases would store a strong reference and + keep the blob alive. "Explicit is better than implicit", so we ask + the user to keep a reference to the original blob alive as long as + it may be used (instead of doing the right things in 90% of the cases + but still crashing in the remaining 10%). + +* LuaJIT uses ``struct foo &`` for a number of things, like for ``p[0]`` + if ``p`` is a ``struct foo *``. I suppose it's not a bad idea at least + to have internally such types, even if you can't specify them through + pycparser. Basically ``struct foo &`` is a type that doesn't own a + blob, whereas ``struct foo`` is the type that does. + +* LuaJIT uses ``int[?]`` which pycparser doesn't accept. I propose + instead to use ``int[]`` for the same purpose (its use is anyway quite + close to the C standard's use of ``int[]``). diff --git a/doc/misc/grant-cffi-1.0.rst b/doc/misc/grant-cffi-1.0.rst new file mode 100644 index 0000000..b026209 --- /dev/null +++ b/doc/misc/grant-cffi-1.0.rst @@ -0,0 +1,124 @@ + +=========================== +Grant Proposal for CFFI 1.0 +=========================== + +*Accepted by the PSF board on April 4, 2015* + +This Grant Proposal is to give a boost towards "CFFI 1.0". Two main +issues with the current CFFI need to be solved: the difficulties of +installation, and the potentially large time taken at import. + +1. The difficulties of installation can be seen from outside by looking +at various workarounds and 3rd-party documentation that have grown into +existence. For example, the `setup.py` of projects like cryptography, +PyNaCl and bcrypt deploys workarounds that are explicitly documented in +https://caremad.io/2014/11/distributing-a-cffi-project/. + +2. The time taken at import is excessive in some cases. For example, +importing `pygame-cffi` on a Raspberry Pi ARM board takes on the order +of 10 to 20 seconds (and this is the "fast" case where the compiler +doesn't need to be invoked any more). + + +Technical Overview +------------------ + +"CFFI" is an existing Python project which complements the ctypes, +SWIG and Cython approaches to ease writing C Extension Modules for +Python. It has several advantages over the previous approaches, which +are presented at the start of the documentation at +http://cffi.readthedocs.org/en/latest/ . It has been very successful +so far: http://pypi-ranking.info/alltime records almost 7 million +downloads (for comparison, the #1 of all packages has almost 36 +million downloads). CFFI works on any Python >= 2.6, including 3.x, +as well as on PyPy. + +One problem is that while getting started with CFFI is very easy, the +installation process of a package that uses CFFI has got its rough +edges. CFFI (at least in its "verify()" mode) is based on calling the +C compiler to get information about the exact C types, structures, +argument types to functions, and so on. The C compiler is invoked +transparently at run-time, and the results cached. A +correctly-installed package using CFFI should cache the results at +installation time, but it can be difficult to ensure that no more +run-time compiler invocation is needed; doing so requires following +some extra guidelines or understanding some internal details. (The +problem is particularly acute on Windows where a typical user might +not have a proper C compiler installed.) + +To fix this, we have in mind adding a different CFFI mode (replacing +"verify()"), while keeping the access to the underlying C library +unmodified. In this mode, the code containing the cdef() and verify() +invocations would be moved to a separate Python source file. Running +that Python file would produce a dynamically-linked library. There +would be no caching logic involved; you would need to run it +explicitly during development whenever you made changes to it, to +re-generate and re-compile the dynamically-linked library. + +When distributed, the same file would be run (once) during +installation. This can be fully automated in setuptools-based +setup.py files; alternatively, it can be done in distutils-based +setup.py files by requiring prior manual installation of CFFI itself. + +A major difference with the existing verify() approach would be that +the ``.so/.dll/.dylib`` file would not be immediately loaded into the +process; you would load it only from the installed program at +run-time, and get the ``ffi`` and ``lib`` objects in this way (these +are the two objects that you use so far to access a C library with +verify()). + +Additionally, this would solve another issue: every import of a large +CFFI-using package takes a while so far. This is caused by CFFI +needing to parse again the C source code given in the cdef() (adding a +run-time dependency to the ``pycparser`` and ``ply`` packages). CFFI +also computes a CRC to know if it can reuse its cache. In the +proposed change, all the cdef() code would be pre-parsed and stored in +the dynamically-linked library, and no CRC would be needed. This +would massively reduce the import times. + + +Grant objective +--------------- + +The objective is to give a boost towards "CFFI 1.0", which needs to have +the functionalities described above in order to solve the two main +issues with the current CFFI: the difficulties of installation, and the +time taken at import. + +Included in the objective: the internal refactorings of CFFI that are +needed to get it done cleanly. The goal is to avoid simply adding +another layer on top of the old unchanged CFFI. + +This work may happen eventually in any case, but support from the PSF +would help make it happen sooner rather than later. + + +Grant size +---------- + +2'500 US$ for supporting the development time. This would cover 2.5 +weeks of full-time work at the part-time cost of 25 US$ per hour. + +The estimated work time until the CFFI 1.0 release is a bit larger +than that (I estimate it at roughly 4 weeks), but 2.5 weeks should +cover all the basics. An extended grant size of 4'000 US$ would be +appreciated but not required ``:-)`` + + +Grant beneficiaries +------------------- + +Armin Rigo, main author of CFFI, committing 2.5 weeks of full-time +work. + + +Grant follow-up +--------------- + +I will report on the success of the grant on the CFFI mailing list and +on the blog I usually post to (the PyPy blog) and mention the PSF as +providing the grant. The PSF will receive an email pointing to these +postings once they are out. Moreover a full CFFI 1.0 release should +follow (likely starting with beta versions); the PSF will receive +another email pointing to it. diff --git a/doc/misc/parse_c_type.rst b/doc/misc/parse_c_type.rst new file mode 100644 index 0000000..1d1029d --- /dev/null +++ b/doc/misc/parse_c_type.rst @@ -0,0 +1,72 @@ +================================================== +CPython C extension module produced by recompile() +================================================== + +Global variable:: + + _cffi_opcode_t _cffi_types[]; + +Every _cffi_types entry is initially an odd integer. At runtime, it +is fixed to be a `CTypeDescrObject *` when the odd integer is +interpreted and turned into a real object. + +The generated C functions are listed in _cffi_globals, a sorted array +of entries which get turned lazily into real . Each entry in this array has an index in the _cffi_types +array, which describe the function type (OP_FUNCTION opcode, see +below). We turn the odd integers describing argument and return types +into real CTypeDescrObjects at the point where the entry is turned +into a real builtin function object. + +The odd integers are "opcodes" that contain a type info in the lowest +byte. The remaining high bytes of the integer is an "arg" that depends +on the type info: + +OP_PRIMITIVE + the arg tells which primitive type it is (an index in some list) + +OP_POINTER + the arg is the index of the item type in the _cffi_types array. + +OP_ARRAY + the arg is the index of the item type in the _cffi_types array. + followed by another opcode that contains (uintptr_t)length_of_array. + +OP_OPEN_ARRAY + for syntax like "int[]". same as OP_ARRAY but without the length + +OP_STRUCT_UNION + the arg is the index of the struct/union in _cffi_structs_unions + +OP_ENUM + the arg is the index of the enum in _cffi_enums + +OP_TYPENAME + the arg is the index of the typename in _cffi_typenames + +OP_FUNCTION + the arg is the index of the result type in _cffi_types. + followed by other opcodes for the arguments. + terminated by OP_FUNCTION_END. + +OP_FUNCTION_END + the arg's lowest bit is set if there is a "..." argument. + +OP_NOOP + simple indirection: the arg is the index to look further in + +There are other opcodes, used not inside _cffi_types but in other +individual ``type_op`` fields. Most importantly, these are used +on _cffi_globals entries: + +OP_CPYTHON_BLTN_* + declare a function + +OP_CONSTANT + declare a non-integral constant + +OP_CONSTANT_INT + declare an int constant + +OP_GLOBAL_VAR + declare a global var diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst new file mode 100644 index 0000000..f0bc6ba --- /dev/null +++ b/doc/source/cdef.rst @@ -0,0 +1,992 @@ +====================================== +Preparing and Distributing modules +====================================== + +.. contents:: + +There are three or four different ways to use CFFI in a project. +In order of complexity: + +* The **"in-line", "ABI mode"**: + + .. code-block:: python + + import cffi + + ffi = cffi.FFI() + ffi.cdef("C-like declarations") + lib = ffi.dlopen("libpath") + + # use ffi and lib here + +.. _out-of-line-abi: + +* The **"out-of-line",** but still **"ABI mode",** useful to organize + the code and reduce the import time: + + .. code-block:: python + + # in a separate file "package/foo_build.py" + import cffi + + ffibuilder = cffi.FFI() + ffibuilder.set_source("package._foo", None) + ffibuilder.cdef("C-like declarations") + + if __name__ == "__main__": + ffibuilder.compile() + + Running ``python foo_build.py`` produces a file ``_foo.py``, which + can then be imported in the main program: + + .. code-block:: python + + from package._foo import ffi + lib = ffi.dlopen("libpath") + + # use ffi and lib here + +.. _out-of-line-api: + +* The **"out-of-line", "API mode"** gives you the most flexibility + and speed to access a C library at the level of C, instead of at the + binary level: + + .. code-block:: python + + # in a separate file "package/foo_build.py" + import cffi + + ffibuilder = cffi.FFI() + ffibuilder.set_source("package._foo", r"""real C code""") # <= + ffibuilder.cdef("C-like declarations with '...'") + + if __name__ == "__main__": + ffibuilder.compile(verbose=True) + + Running ``python foo_build.py`` produces a file ``_foo.c`` and + invokes the C compiler to turn it into a file ``_foo.so`` (or + ``_foo.pyd`` or ``_foo.dylib``). It is a C extension module which + can be imported in the main program: + + .. code-block:: python + + from package._foo import ffi, lib + # no ffi.dlopen() + + # use ffi and lib here + +.. _distutils-setuptools: + +* Finally, you can (but don't have to) use CFFI's **Distutils** or + **Setuptools integration** when writing a ``setup.py``. For + Distutils (only in out-of-line API mode): + + .. code-block:: python + + # setup.py (requires CFFI to be installed first) + from distutils.core import setup + + import foo_build # possibly with sys.path tricks to find it + + setup( + ..., + ext_modules=[foo_build.ffibuilder.distutils_extension()], + ) + + For Setuptools (out-of-line, but works in ABI or API mode; + recommended): + + .. code-block:: python + + # setup.py (with automatic dependency tracking) + from setuptools import setup + + setup( + ..., + setup_requires=["cffi>=1.0.0"], + cffi_modules=["package/foo_build.py:ffibuilder"], + install_requires=["cffi>=1.0.0"], + ) + + Note again that the ``foo_build.py`` example contains the following + lines, which mean that the ``ffibuilder`` is not actually compiled + when ``package.foo_build`` is merely imported---it will be compiled + independently by the Setuptools logic, using compilation parameters + provided by Setuptools: + + .. code-block:: python + + if __name__ == "__main__": # not when running with setuptools + ffibuilder.compile(verbose=True) + +* Note that some bundler tools that try to find all modules used by a + project, like PyInstaller, will miss ``_cffi_backend`` in the + out-of-line mode because your program contains no explicit ``import + cffi`` or ``import _cffi_backend``. You need to add + ``_cffi_backend`` explicitly (as a "hidden import" in PyInstaller, + but it can also be done more generally by adding the line ``import + _cffi_backend`` in your main program). + +Note that CFFI actually contains two different ``FFI`` classes. The +page `Using the ffi/lib objects`_ describes the common functionality. +It is what you get in the ``from package._foo import ffi`` lines above. +On the other hand, the extended ``FFI`` class is the one you get from +``import cffi; ffi_or_ffibuilder = cffi.FFI()``. It has the same +functionality (for in-line use), but also the extra methods described +below (to prepare the FFI). NOTE: We use the name ``ffibuilder`` +instead of ``ffi`` in the out-of-line context, when the code is about +producing a ``_foo.so`` file; this is an attempt to distinguish it +from the different ``ffi`` object that you get by later saying +``from _foo import ffi``. + +.. _`Using the ffi/lib objects`: using.html + +The reason for this split of functionality is that a regular program +using CFFI out-of-line does not need to import the ``cffi`` pure +Python package at all. (Internally it still needs ``_cffi_backend``, +a C extension module that comes with CFFI; this is why CFFI is also +listed in ``install_requires=..`` above. In the future this might be +split into a different PyPI package that only installs +``_cffi_backend``.) + +Note that a few small differences do exist: notably, ``from _foo import +ffi`` returns an object of a type written in C, which does not let you +add random attributes to it (nor does it have all the +underscore-prefixed internal attributes of the Python version). +Similarly, the ``lib`` objects returned by the C version are read-only, +apart from writes to global variables. Also, ``lib.__dict__`` does +not work before version 1.2 or if ``lib`` happens to declare a name +called ``__dict__`` (use instead ``dir(lib)``). The same is true +for ``lib.__class__``, ``lib.__all__`` and ``lib.__name__`` added +in successive versions. + + +.. _cdef: + +ffi/ffibuilder.cdef(): declaring types and functions +---------------------------------------------------- + +**ffi/ffibuilder.cdef(source)**: parses the given C source. +It registers all the functions, types, constants and global variables in +the C source. The types can be used immediately in ``ffi.new()`` and +other functions. Before you can access the functions and global +variables, you need to give ``ffi`` another piece of information: where +they actually come from (which you do with either ``ffi.dlopen()`` or +``ffi.set_source()``). + +.. _`all types listed above`: + +The C source is parsed internally (using ``pycparser``). This code +cannot contain ``#include``. It should typically be a self-contained +piece of declarations extracted from a man page. The only things it +can assume to exist are the standard types: + +* char, short, int, long, long long (both signed and unsigned) + +* float, double, long double + +* intN_t, uintN_t (for N=8,16,32,64), intptr_t, uintptr_t, ptrdiff_t, + size_t, ssize_t + +* wchar_t (if supported by the backend). *New in version 1.11:* + char16_t and char32_t. + +* _Bool and bool (equivalent). If not directly supported by the C + compiler, this is declared with the size of ``unsigned char``. + +* FILE. `See here.`__ + +* all `common Windows types`_ are defined if you run + on Windows (``DWORD``, ``LPARAM``, etc.). Exception: + ``TBYTE TCHAR LPCTSTR PCTSTR LPTSTR PTSTR PTBYTE PTCHAR`` are + not automatically defined; see `ffi.set_unicode()`_. + +* the other standard integer types from + stdint.h, like ``intmax_t``, as long as they map to integers of 1, + 2, 4 or 8 bytes. Larger integers are not supported. + +.. __: ref.html#file +.. _`common Windows types`: http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751%28v=vs.85%29.aspx + +The declarations can also contain "``...``" at various places; these are +placeholders that will be completed by the compiler. More information +about it below in `Letting the C compiler fill the gaps`_. + +Note that all standard type names listed above are handled as +*defaults* only (apart from the ones that are keywords in the C +language). If your ``cdef`` contains an explicit typedef that +redefines one of the types above, then the default described above is +ignored. (This is a bit hard to implement cleanly, so in some corner +cases it might fail, notably with the error ``Multiple type specifiers +with a type tag``. Please report it as a bug if it does.) + +Multiple calls to ``ffi.cdef()`` are possible. Beware that it can be +slow to call ``ffi.cdef()`` a lot of times, a consideration that is +important mainly in in-line mode. + +The ``ffi.cdef()`` call optionally takes an extra argument: either +``packed`` or ``pack``. If you pass ``packed=True``, +then all structs declared within +this cdef are "packed". (If you need both packed and non-packed +structs, use several cdefs in sequence.) This +has a meaning similar to ``__attribute__((packed))`` in GCC. It +specifies that all structure fields should have an alignment of one +byte. (Note that the packed attribute has no effect on bit fields so +far, which mean that they may be packed differently than on GCC. +Also, this has no effect on structs declared with ``"...;"``---more +about it later in `Letting the C compiler fill the gaps`_.) +*New in version 1.12:* In ABI mode, you can also pass ``pack=n``, +with an integer ``n`` which must be a power of two. Then the +alignment of any field is limited to ``n`` if it would otherwise be +greater than ``n``. Passing ``pack=1`` is equivalent to passing +``packed=True``. This is meant to emulate ``#pragma pack(n)`` from +the MSVC compiler. On Windows, the default is ``pack=8`` (from cffi +1.12 onwards); on other platforms, the default is ``pack=None``. + +Note that you can use the type-qualifiers ``const`` and ``restrict`` +(but not ``__restrict`` or ``__restrict__``) in the ``cdef()``, but +this has no effect on the cdata objects that you get at run-time (they +are never ``const``). The effect is limited to knowing if a global +variable is meant to be a constant or not. Also, *new in version +1.3:* when using ``set_source()`` or ``verify()``, these two +qualifiers are copied from the cdef to the generated C code; this +fixes warnings by the C compiler. + +Note a trick if you copy-paste code from sources in which there are +extra macros (for example, the Windows documentation uses SAL +annotations like ``_In_`` or ``_Out_``). These hints must be removed +in the string given to cdef(), but it can be done programmatically +like this:: + + ffi.cdef(re.sub(r"\b(_In_|_Inout_|_Out_|_Outptr_)(opt_)?\b", " ", + """ + DWORD WINAPI GetModuleFileName( + _In_opt_ HMODULE hModule, + _Out_ LPTSTR lpFilename, + _In_ DWORD nSize + ); + """)) + +Note also that pycparser, the underlying C parser, recognizes +preprocessor-like directives in the following format: ``# NUMBER +"FILE"``. For example, if you put ``# 42 "foo.h"`` in the middle of the +string passed to ``cdef()`` and there is an error two lines later, then +it is reported with an error message that starts with ``foo.h:43:`` (the +line which is given the number 42 is the line immediately after the +directive). *New in version 1.10.1:* CFFI automatically puts the line +``# 1 ""`` just before the string you give to +``cdef()``. + + +.. _`ffi.set_unicode()`: + +**ffi.set_unicode(enabled_flag)**: Windows: if ``enabled_flag`` is +True, enable the ``UNICODE`` and ``_UNICODE`` defines in C, and +declare the types ``TBYTE TCHAR LPCTSTR PCTSTR LPTSTR PTSTR PTBYTE +PTCHAR`` to be (pointers to) ``wchar_t``. If ``enabled_flag`` is +False, declare these types to be (pointers to) plain 8-bit characters. +(These types are not predeclared at all if you don't call +``set_unicode()``.) + +The reason behind this method is that a lot of standard functions have +two versions, like ``MessageBoxA()`` and ``MessageBoxW()``. The +official interface is ``MessageBox()`` with arguments like +``LPTCSTR``. Depending on whether ``UNICODE`` is defined or not, the +standard header renames the generic function name to one of the two +specialized versions, and declares the correct (unicode or not) types. + +Usually, the right thing to do is to call this method with True. Be +aware (particularly on Python 2) that, afterwards, you need to pass unicode +strings as arguments instead of byte strings. + + +.. _loading-libraries: + +ffi.dlopen(): loading libraries in ABI mode +------------------------------------------- + +``ffi.dlopen(libpath, [flags])``: this function opens a shared library and +returns a module-like library object. Use this when you are fine with +the limitations of ABI-level access to the system (dependency on ABI +details, getting crashes instead of C compiler errors/warnings, and +higher overhead to call the C functions). In case of doubt, read again +`ABI versus API`_ in the overview. + +.. _`ABI versus API`: overview.html#abi-versus-api + +You can use the library object to call the functions previously +declared by ``ffi.cdef()``, to read constants, and to read or write +global variables. Note that you can use a single ``cdef()`` to +declare functions from multiple libraries, as long as you load each of +them with ``dlopen()`` and access the functions from the correct one. + +The ``libpath`` is the file name of the shared library, which can +contain a full path or not (in which case it is searched in standard +locations, as described in ``man dlopen``), with extensions or not. +Alternatively, if ``libpath`` is None, it returns the standard C library +(which can be used to access the functions of glibc, on Linux). Note +that ``libpath`` `cannot be None`__ on Windows with Python 3. + +.. __: http://bugs.python.org/issue23606 + +Let me state it again: this gives ABI-level access to the library, so +you need to have all types declared manually exactly as they were +while the library was made. No checking is done. Mismatches can +cause random crashes. API-level access, on the other hand, is safer. +Speed-wise, API-level access is much faster (it is common to have +the opposite misconception about performance). + +Note that only functions and global variables live in library objects; +the types exist in the ``ffi`` instance independently of library objects. +This is due to the C model: the types you declare in C are not tied to a +particular library, as long as you ``#include`` their headers; but you +cannot call functions from a library without linking it in your program, +as ``dlopen()`` does dynamically in C. + +For the optional ``flags`` argument, see ``man dlopen`` (ignored on +Windows). It defaults to ``ffi.RTLD_NOW``. + +This function returns a "library" object that gets closed when it goes +out of scope. Make sure you keep the library object around as long as +needed. (Alternatively, the out-of-line FFIs have a method +``ffi.dlclose(lib)``.) + +.. _dlopen-note: + +Note: the old version of ``ffi.dlopen()`` from the in-line ABI mode +tries to use ``ctypes.util.find_library()`` if it cannot directly find +the library. The newer out-of-line ``ffi.dlopen()`` no longer does it +automatically; it simply passes the argument it receives to the +underlying ``dlopen()`` or ``LoadLibrary()`` function. If needed, it +is up to you to use ``ctypes.util.find_library()`` or any other way to +look for the library's filename. This also means that +``ffi.dlopen(None)`` no longer work on Windows; try instead +``ffi.dlopen(ctypes.util.find_library('c'))``. + + +ffibuilder.set_source(): preparing out-of-line modules +------------------------------------------------------ + +**ffibuilder.set_source(module_name, c_header_source, [\*\*keywords...])**: +prepare the ffi for producing out-of-line an external module called +``module_name``. + +``ffibuilder.set_source()`` by itself does not write any file, but merely +records its arguments for later. It can therefore be called before or +after ``ffibuilder.cdef()``. + +In **ABI mode,** you call ``ffibuilder.set_source(module_name, None)``. The +argument is the name (or dotted name inside a package) of the Python +module to generate. In this mode, no C compiler is called. + +In **API mode,** the ``c_header_source`` argument is a string that +will be pasted into the .c file generated. Typically, it is specified as +``r""" ...multiple lines of C code... """`` (the ``r`` prefix allows these +lines to contain a literal ``\n``, for example). This piece of C code +typically contains some ``#include``, but may also contain more, +like definitions for custom "wrapper" C functions. The goal is that +the .c file can be generated like this:: + + // C file "module_name.c" + #include + + ...c_header_source... + + ...magic code... + +where the "magic code" is automatically generated from the ``cdef()``. +For example, if the ``cdef()`` contains ``int foo(int x);`` then the +magic code will contain logic to call the function ``foo()`` with an +integer argument, itself wrapped inside some CPython or PyPy-specific +code. + +The keywords arguments to ``set_source()`` control how the C compiler +will be called. They are passed directly to distutils_ or setuptools_ +and include at least ``sources``, ``include_dirs``, ``define_macros``, +``undef_macros``, ``libraries``, ``library_dirs``, ``extra_objects``, +``extra_compile_args`` and ``extra_link_args``. You typically need at +least ``libraries=['foo']`` in order to link with ``libfoo.so`` or +``libfoo.so.X.Y``, or ``foo.dll`` on Windows. The ``sources`` is a +list of extra .c files compiled and linked together (the file +``module_name.c`` shown above is always generated and automatically added as the +first argument to ``sources``). See the distutils documentations for +`more information about the other arguments`__. + +.. __: http://docs.python.org/distutils/setupscript.html#library-options +.. _distutils: http://docs.python.org/distutils/setupscript.html#describing-extension-modules +.. _setuptools: https://pythonhosted.org/setuptools/setuptools.html + +An extra keyword argument processed internally is +``source_extension``, defaulting to ``".c"``. The file generated will +be actually called ``module_name + source_extension``. Example for +C++ (but note that there are still a few known issues of C-versus-C++ +compatibility): + +.. code-block:: python + + ffibuilder.set_source("mymodule", r''' + extern "C" { + int somefunc(int somearg) { return real_cpp_func(somearg); } + } + ''', source_extension='.cpp') + +.. _pkgconfig: + +**ffibuilder.set_source_pkgconfig(module_name, pkgconfig_libs, +c_header_source, [\*\*keywords...])**: + +*New in version 1.12.* This is equivalent to ``set_source()`` but it +first calls the system utility ``pkg-config`` with the package names +given in the list ``pkgconfig_libs``. It collects the information +obtained in this way and adds it to the explicitly-provided +``**keywords`` (if any). This should probably not be used on Windows. + +If the ``pkg-config`` program is not installed or does not know about +the requested library, the call fails with ``cffi.PkgConfigError``. If +necessary, you can catch this error and try to call ``set_source()`` +directly. (Ideally, you should also do that if the ``ffibuilder`` +instance has no method ``set_source_pkgconfig()``, to support older +versions of cffi.) + + +Letting the C compiler fill the gaps +------------------------------------ + +If you are using a C compiler ("API mode"), then: + +* functions taking or returning integer or float-point arguments can be + misdeclared: if e.g. a function is declared by ``cdef()`` as taking a + ``int``, but actually takes a ``long``, then the C compiler handles the + difference. + +* other arguments are checked: you get a compilation warning or error + if you pass a ``int *`` argument to a function expecting a ``long *``. + +* similarly, most other things declared in the ``cdef()`` are checked, + to the best we implemented so far; mistakes give compilation + warnings or errors. + +Moreover, you can use "``...``" (literally, dot-dot-dot) in the +``cdef()`` at various places, in order to ask the C compiler to fill +in the details. These places are: + +* structure declarations: any ``struct { }`` that ends with "``...;``" as + the last "field" is + partial: it may be missing fields and/or have them declared out of order. + This declaration will be corrected by the compiler. (But note that you + can only access fields that you declared, not others.) Any ``struct`` + declaration which doesn't use "``...``" is assumed to be exact, but this is + checked: you get an error if it is not correct. + +* integer types: the syntax "``typedef + int... foo_t;``" declares the type ``foo_t`` as an integer type + whose exact size and signedness is not specified. The compiler will + figure it out. (Note that this requires ``set_source()``; it does + not work with ``verify()``.) The ``int...`` can be replaced with + ``long...`` or ``unsigned long long...`` or any other primitive + integer type, with no effect. The type will always map to one of + ``(u)int(8,16,32,64)_t`` in Python, but in the generated C code, + only ``foo_t`` is used. + +* *New in version 1.3:* floating-point types: "``typedef + float... foo_t;``" (or equivalently "``typedef double... foo_t;``") + declares ``foo_t`` as a-float-or-a-double; the compiler will figure + out which it is. Note that if the actual C type is even larger + (``long double`` on some platforms), then compilation will fail. + The problem is that the Python "float" type cannot be used to store + the extra precision. (Use the non-dot-dot-dot syntax ``typedef long + double foo_t;`` as usual, which returns values that are not Python + floats at all but cdata "long double" objects.) + +* unknown types: the syntax "``typedef ... foo_t;``" declares the type + ``foo_t`` as opaque. Useful mainly for when the API takes and returns + ``foo_t *`` without you needing to look inside the ``foo_t``. Also + works with "``typedef ... *foo_p;``" which declares the pointer type + ``foo_p`` without giving a name to the opaque type itself. Note that + such an opaque struct has no known size, which prevents some operations + from working (mostly like in C). *You cannot use this syntax to + declare a specific type, like an integer type! It declares opaque + struct-like types only.* In some cases you need to say that + ``foo_t`` is not opaque, but just a struct where you don't know any + field; then you would use "``typedef struct { ...; } foo_t;``". + +* array lengths: when used as structure fields or in global variables, + arrays can have an unspecified length, as in "``int n[...];``". The + length is completed by the C compiler. + This is slightly different from "``int n[];``", because the latter + means that the length is not known even to the C compiler, and thus + no attempt is made to complete it. This supports + multidimensional arrays: "``int n[...][...];``". + + *New in version 1.2:* "``int m[][...];``", i.e. ``...`` can be used + in the innermost dimensions without being also used in the outermost + dimension. In the example given, the length of the ``m`` array is + assumed not to be known to the C compiler, but the length of every + item (like the sub-array ``m[0]``) is always known the C compiler. + In other words, only the outermost dimension can be specified as + ``[]``, both in C and in CFFI, but any dimension can be given as + ``[...]`` in CFFI. + +* enums: if you don't know the exact order (or values) of the declared + constants, then use this syntax: "``enum foo { A, B, C, ... };``" + (with a trailing "``...``"). The C compiler will be used to figure + out the exact values of the constants. An alternative syntax is + "``enum foo { A=..., B, C };``" or even + "``enum foo { A=..., B=..., C=... };``". Like + with structs, an ``enum`` without "``...``" is assumed to + be exact, and this is checked. + +* integer constants and macros: you can write in the ``cdef`` the line + "``#define FOO ...``", with any macro name FOO but with ``...`` as + a value. Provided the macro + is defined to be an integer value, this value will be available via + an attribute of the library object. The + same effect can be achieved by writing a declaration + ``static const int FOO;``. The latter is more general because it + supports other types than integer types (note: the C syntax is then + to write the ``const`` together with the variable name, as in + ``static char *const FOO;``). + +Currently, it is not supported to find automatically which of the +various integer or float types you need at which place---except in the +following case: if such a type is explicitly named. For an integer +type, use ``typedef int... the_type_name;``, or another type like +``typedef unsigned long... the_type_name;``. Both are equivalent and +replaced by the real C type, which must be an integer type. +Similarly, for floating-point types, use ``typedef float... +the_type_name;`` or equivalently ``typedef double... the_type_name;``. +Note that ``long double`` cannot be detected this way. + +In the case of function arguments or return types, when it is a simple +integer/float type, you can simply misdeclare it. If you misdeclare a +function ``void f(long)`` as ``void f(int)``, it still works (but you +have to call it with arguments that fit an int). It works because the C +compiler will do the casting for us. This C-level casting of arguments +and return types only works for regular function, and not for function +pointer types; currently, it also does not work for variadic functions. + +For more complex types, you have no choice but be precise. For example, +you cannot misdeclare a ``int *`` argument as ``long *``, or a global +array ``int a[5];`` as ``long a[5];``. CFFI considers `all types listed +above`_ as primitive (so ``long long a[5];`` and ``int64_t a[5]`` are +different declarations). The reason for that is detailed in `a comment +about an issue.`__ + +.. __: https://bitbucket.org/cffi/cffi/issues/265/cffi-doesnt-allow-creating-pointers-to#comment-28406958 + + +ffibuilder.compile() etc.: compiling out-of-line modules +-------------------------------------------------------- + +You can use one of the following functions to actually generate the +.py or .c file prepared with ``ffibuilder.set_source()`` and +``ffibuilder.cdef()``. + +Note that these function won't overwrite a .py/.c file with exactly +the same content, to preserve the mtime. In some cases where you need +the mtime to be updated anyway, delete the file before calling the +functions. + +*New in version 1.8:* the C code produced by ``emit_c_code()`` or +``compile()`` contains ``#define Py_LIMITED_API``. This means that on +CPython >= 3.2, compiling this source produces a binary .so/.dll that +should work for any version of CPython >= 3.2 (as opposed to only for +the same version of CPython x.y). However, the standard ``distutils`` +package will still produce a file called e.g. +``NAME.cpython-35m-x86_64-linux-gnu.so``. You can manually rename it to +``NAME.abi3.so``, or use setuptools version 26 or later. Also, note +that compiling with a debug version of Python will not actually define +``Py_LIMITED_API``, as doing so makes ``Python.h`` unhappy. + +*New in version 1.12:* ``Py_LIMITED_API`` is now defined on Windows too. +If you use ``virtualenv``, you need a recent version of it: versions +older than 16.0.0 forgot to copy ``python3.dll`` into the virtual +environment. In case upgrading ``virtualenv`` is a real problem, you +can manually edit the C code to remove the first line ``# define +Py_LIMITED_API``. + +**ffibuilder.compile(tmpdir='.', verbose=False, debug=None):** +explicitly generate the .py or .c file, +and (if .c) compile it. The output file is (or are) put in the +directory given by ``tmpdir``. In the examples given here, we use +``if __name__ == "__main__": ffibuilder.compile()`` in the build scripts---if +they are directly executed, this makes them rebuild the .py/.c file in +the current directory. (Note: if a package is specified in the call +to ``set_source()``, then a corresponding subdirectory of the ``tmpdir`` +is used.) + +*New in version 1.4:* ``verbose`` argument. If True, it prints the +usual distutils output, including the command lines that call the +compiler. (This parameter might be changed to True by default in a +future release.) + +*New in version 1.8.1:* ``debug`` argument. If set to a bool, it +controls whether the C code is compiled in debug mode or not. The +default None means to use the host Python's ``sys.flags.debug``. +Starting with version 1.8.1, if you are running a debug-mode Python, the +C code is thus compiled in debug mode by default (note that it is anyway +necessary to do so on Windows). + +**ffibuilder.emit_python_code(filename):** generate the given .py file (same +as ``ffibuilder.compile()`` for ABI mode, with an explicitly-named file to +write). If you choose, you can include this .py file pre-packaged in +your own distributions: it is identical for any Python version (2 or +3). + +**ffibuilder.emit_c_code(filename):** generate the given .c file (for API +mode) without compiling it. Can be used if you have some other method +to compile it, e.g. if you want to integrate with some larger build +system that will compile this file for you. You can also distribute +the .c file: unless the build script you used depends on the OS or +platform, the .c file itself is generic (it would be exactly the same +if produced on a different OS, with a different version of CPython, or +with PyPy; it is done with generating the appropriate ``#ifdef``). + +**ffibuilder.distutils_extension(tmpdir='build', verbose=True):** for +distutils-based ``setup.py`` files. Calling this creates the .c file +if needed in the given ``tmpdir``, and returns a +``distutils.core.Extension`` instance. + +For Setuptools, you use instead the line +``cffi_modules=["path/to/foo_build.py:ffibuilder"]`` in ``setup.py``. This +line asks Setuptools to import and use a helper provided by CFFI, +which in turn executes the file ``path/to/foo_build.py`` (as with +``execfile()``) and looks up its global variable called ``ffibuilder``. You +can also say ``cffi_modules=["path/to/foo_build.py:maker"]``, where +``maker`` names a global function; it is called with no argument and +is supposed to return a ``FFI`` object. + + +ffi/ffibuilder.include(): combining multiple CFFI interfaces +------------------------------------------------------------ + +**ffi/ffibuilder.include(other_ffi)**: includes the typedefs, structs, unions, +enums and constants defined in another FFI instance. This is meant +for large projects where one CFFI-based interface depends on some +types declared in a different CFFI-based interface. + +*Note that you should only use one ffi object per library; the intended +usage of ffi.include() is if you want to interface with several +inter-dependent libraries.* For only one library, make one ``ffi`` +object. (You can write several ``cdef()`` calls over the same ``ffi`` +from several Python files, if one file would be too large.) + +For out-of-line modules, the ``ffibuilder.include(other_ffibuilder)`` +line should +occur in the build script, and the ``other_ffibuilder`` argument should be +another FFI instance that comes from another build script. When the two build +scripts are turned into generated files, say ``_ffi.so`` and +``_other_ffi.so``, then importing ``_ffi.so`` will internally cause +``_other_ffi.so`` to be imported. At that point, the real +declarations from ``_other_ffi.so`` are combined with the real +declarations from ``_ffi.so``. + +The usage of ``ffi.include()`` is the cdef-level equivalent of a +``#include`` in C, where a part of the program might include types and +functions defined in another part for its own usage. You can see on +the ``ffi`` object (and associated ``lib`` objects on the *including* +side) the types and constants declared on the included side. In API +mode, you can also see the functions and global variables directly. +In ABI mode, these must be accessed via the original ``other_lib`` +object returned by the ``dlopen()`` method on ``other_ffi``. + + +ffi.cdef() limitations +---------------------- + +All of the ANSI C *declarations* should be supported in ``cdef()``, +and some of C99. (This excludes any ``#include`` or ``#ifdef``.) +Known missing features that are either in C99, or are GCC or MSVC +extensions: + +* Any ``__attribute__`` or ``#pragma pack(n)`` + +* Additional types: special-size floating and fixed + point types, vector types, and so on. + +* The C99 types ``float _Complex`` and ``double _Complex`` are supported + by cffi since version 1.11, but not libffi: you cannot call C + functions with complex arguments or return value, except if they are + directly API-mode functions. The type ``long double _Complex`` is not + supported at all (declare and use it as if it were an array of two + ``long double``, and write wrapper functions in C with set_source()). + +* ``__restrict__`` or ``__restrict`` are extensions of, respectively, + GCC and MSVC. They are not recognized. But ``restrict`` is a C + keyword and is accepted (and ignored). + +Note that declarations like ``int field[];`` in +structures are interpreted as variable-length structures. Declarations +like ``int field[...];`` on the other hand are arrays whose length is +going to be completed by the compiler. You can use ``int field[];`` +for array fields that are not, in fact, variable-length; it works too, +but in this case, as CFFI +believes it cannot ask the C compiler for the length of the array, you +get reduced safety checks: for example, you risk overwriting the +following fields by passing too many array items in the constructor. + +*New in version 1.2:* +Thread-local variables (``__thread``) can be accessed, as well as +variables defined as dynamic macros (``#define myvar (*fetchme())``). +Before version 1.2, you need to write getter/setter functions. + +Note that if you declare a variable in ``cdef()`` without using +``const``, CFFI assumes it is a read-write variable and generates two +pieces of code, one to read it and one to write it. If the variable +cannot in fact be written to in C code, for one reason or another, it +will not compile. In this case, you can declare it as a constant: for +example, instead of ``foo_t *myglob;`` you would use ``foo_t *const +myglob;``. Note also that ``const foo_t *myglob;`` is a *variable;* it +contains a variable pointer to a constant ``foo_t``. + + +Debugging dlopen'ed C libraries +------------------------------- + +A few C libraries are actually hard to use correctly in a ``dlopen()`` +setting. This is because most C libraries are intended for, and tested +with, a situation where they are *linked* with another program, using +either static linking or dynamic linking --- but from a program written +in C, at start-up, using the linker's capabilities instead of +``dlopen()``. + +This can occasionally create issues. You would have the same issues in +another setting than CFFI, like with ``ctypes`` or even plain C code that +calls ``dlopen()``. This section contains a few generally useful +environment variables (on Linux) that can help when debugging these +issues. + +**export LD_TRACE_LOADED_OBJECTS=all** + + provides a lot of information, sometimes too much depending on the + setting. Output verbose debugging information about the dynamic + linker. If set to ``all`` prints all debugging information it has, if + set to ``help`` prints a help message about which categories can be + specified in this environment variable + +**export LD_VERBOSE=1** + + (glibc since 2.1) If set to a nonempty string, output symbol + versioning information about the program if querying information + about the program (i.e., either ``LD_TRACE_LOADED_OBJECTS`` has been set, + or ``--list`` or ``--verify`` options have been given to the dynamic + linker). + +**export LD_WARN=1** + + (ELF only)(glibc since 2.1.3) If set to a nonempty string, warn + about unresolved symbols. + + +ffi.verify(): in-line API-mode +------------------------------ + +**ffi.verify()** is supported for backward compatibility, but is +deprecated. ``ffi.verify(c_header_source, tmpdir=.., ext_package=.., +modulename=.., flags=.., **kwargs)`` makes and compiles a C file from +the ``ffi.cdef()``, like ``ffi.set_source()`` in API mode, and then +immediately loads and returns the dynamic library object. Some +non-trivial logic is used to decide if the dynamic library must be +recompiled or not; see below for ways to control it. + +The ``c_header_source`` and the extra keyword arguments have the +same meaning as in ``ffi.set_source()``. + +One remaining use case for ``ffi.verify()`` would be the following +hack to find explicitly the size of any type, in bytes, and have it +available in Python immediately (e.g. because it is needed in order to +write the rest of the build script): + +.. code-block:: python + + ffi = cffi.FFI() + ffi.cdef("const int mysize;") + lib = ffi.verify("const int mysize = sizeof(THE_TYPE);") + print lib.mysize + +Extra arguments to ``ffi.verify()``: + +* ``tmpdir`` controls where the C + files are created and compiled. Unless the ``CFFI_TMPDIR`` environment + variable is set, the default is + ``directory_containing_the_py_file/__pycache__`` using the + directory name of the .py file that contains the actual call to + ``ffi.verify()``. (This is a bit of a hack but is generally + consistent with the location of the .pyc files for your library. + The name ``__pycache__`` itself comes from Python 3.) + +* ``ext_package`` controls in which package the + compiled extension module should be looked from. This is + only useful after distributing ffi.verify()-based modules. + +* The ``tag`` argument gives an extra string inserted in the + middle of the extension module's name: ``_cffi__``. + Useful to give a bit more context, e.g. when debugging. + +* The ``modulename`` argument can be used to force a specific module + name, overriding the name ``_cffi__``. Use with care, + e.g. if you are passing variable information to ``verify()`` but + still want the module name to be always the same (e.g. absolute + paths to local files). In this case, no hash is computed and if + the module name already exists it will be reused without further + check. Be sure to have other means of clearing the ``tmpdir`` + whenever you change your sources. + +* ``source_extension`` has the same meaning as in ``ffibuilder.set_source()``. + +* The optional ``flags`` argument (ignored on Windows) defaults to + ``ffi.RTLD_NOW``; see ``man dlopen``. (With + ``ffibuilder.set_source()``, you would use ``sys.setdlopenflags()``.) + +* The optional ``relative_to`` argument is useful if you need to list + local files passed to the C compiler:: + + ext = ffi.verify(..., sources=['foo.c'], relative_to=__file__) + + The line above is roughly the same as:: + + ext = ffi.verify(..., sources=['/path/to/this/file/foo.c']) + + except that the default name of the produced library is built from + the CRC checkum of the argument ``sources``, as well as most other + arguments you give to ``ffi.verify()`` -- but not ``relative_to``. + So if you used the second line, it would stop finding the + already-compiled library after your project is installed, because + the ``'/path/to/this/file'`` suddenly changed. The first line does + not have this problem. + +Note that during development, every time you change the C sources that +you pass to ``cdef()`` or ``verify()``, then the latter will create a +new module file name, based on two CRC32 hashes computed from these +strings. This creates more and more files in the ``__pycache__`` +directory. It is recommended that you clean it up from time to time. +A nice way to do that is to add, in your test suite, a call to +``cffi.verifier.cleanup_tmpdir()``. Alternatively, you can manually +remove the whole ``__pycache__`` directory. + +An alternative cache directory can be given as the ``tmpdir`` argument +to ``verify()``, via the environment variable ``CFFI_TMPDIR``, or by +calling ``cffi.verifier.set_tmpdir(path)`` prior to calling +``verify``. + + +Upgrading from CFFI 0.9 to CFFI 1.0 +----------------------------------- + +CFFI 1.0 is backward-compatible, but it is still a good idea to +consider moving to the out-of-line approach new in 1.0. Here are the +steps. + +**ABI mode** if your CFFI project uses ``ffi.dlopen()``: + +.. code-block:: python + + import cffi + + ffi = cffi.FFI() + ffi.cdef("stuff") + lib = ffi.dlopen("libpath") + +and *if* the "stuff" part is big enough that import time is a concern, +then rewrite it as described in `the out-of-line but still ABI mode`__ +above. Optionally, see also the `setuptools integration`__ paragraph. + +.. __: out-of-line-abi_ +.. __: distutils-setuptools_ + + +**API mode** if your CFFI project uses ``ffi.verify()``: + +.. code-block:: python + + import cffi + + ffi = cffi.FFI() + ffi.cdef("stuff") + lib = ffi.verify("real C code") + +then you should really rewrite it as described in `the out-of-line, +API mode`__ above. It avoids a number of issues that have caused +``ffi.verify()`` to grow a number of extra arguments over time. Then +see the `distutils or setuptools`__ paragraph. Also, remember to +remove the ``ext_package=".."`` from your ``setup.py``, which was +sometimes needed with ``verify()`` but is just creating confusion with +``set_source()``. + +.. __: out-of-line-api_ +.. __: distutils-setuptools_ + +The following example should work both with old (pre-1.0) and new +versions of CFFI---supporting both is important to run on old +versions of PyPy (CFFI 1.0 does not work in PyPy < 2.6): + +.. code-block:: python + + # in a separate file "package/foo_build.py" + import cffi + + ffi = cffi.FFI() + C_HEADER_SRC = r''' + #include "somelib.h" + ''' + C_KEYWORDS = dict(libraries=['somelib']) + + if hasattr(ffi, 'set_source'): + ffi.set_source("package._foo", C_HEADER_SRC, **C_KEYWORDS) + + ffi.cdef(''' + int foo(int); + ''') + + if __name__ == "__main__": + ffi.compile() + +And in the main program: + +.. code-block:: python + + try: + from package._foo import ffi, lib + except ImportError: + from package.foo_build import ffi, C_HEADER_SRC, C_KEYWORDS + lib = ffi.verify(C_HEADER_SRC, **C_KEYWORDS) + +(FWIW, this latest trick can be used more generally to allow the +import to "work" even if the ``_foo`` module was not generated.) + +Writing a ``setup.py`` script that works both with CFFI 0.9 and 1.0 +requires explicitly checking the version of CFFI that we can have---it +is hard-coded as a built-in module in PyPy: + +.. code-block:: python + + if '_cffi_backend' in sys.builtin_module_names: # PyPy + import _cffi_backend + requires_cffi = "cffi==" + _cffi_backend.__version__ + else: + requires_cffi = "cffi>=1.0.0" + +Then we use the ``requires_cffi`` variable to give different arguments to +``setup()`` as needed, e.g.: + +.. code-block:: python + + if requires_cffi.startswith("cffi==0."): + # backward compatibility: we have "cffi==0.*" + from package.foo_build import ffi + extra_args = dict( + ext_modules=[ffi.verifier.get_extension()], + ext_package="...", # if needed + ) + else: + extra_args = dict( + setup_requires=[requires_cffi], + cffi_modules=['package/foo_build.py:ffi'], + ) + setup( + name=..., + ..., + install_requires=[requires_cffi], + **extra_args + ) diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..3400cd1 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# CFFI documentation build configuration file, created by +# sphinx-quickstart on Thu Jun 14 16:37:47 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'CFFI' +copyright = u'2012-2018, Armin Rigo, Maciej Fijalkowski' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.12' +# The full version, including alpha/beta/rc tags. +release = '1.12.2' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +#html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'CFFIdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'CFFI.tex', u'CFFI Documentation', + u'Armin Rigo, Maciej Fijalkowski', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/doc/source/embedding.rst b/doc/source/embedding.rst new file mode 100644 index 0000000..181249c --- /dev/null +++ b/doc/source/embedding.rst @@ -0,0 +1,524 @@ +================================ +Using CFFI for embedding +================================ + +.. contents:: + +You can use CFFI to generate C code which exports the API of your choice +to any C application that wants to link with this C code. This API, +which you define yourself, ends up as the API of a ``.so/.dll/.dylib`` +library---or you can statically link it within a larger application. + +Possible use cases: + +* Exposing a library written in Python directly to C/C++ programs. + +* Using Python to make a "plug-in" for an existing C/C++ program that is + already written to load them. + +* Using Python to implement part of a larger C/C++ application (with + static linking). + +* Writing a small C/C++ wrapper around Python, hiding the fact that the + application is actually written in Python (to make a custom + command-line interface; for distribution purposes; or simply to make + it a bit harder to reverse-engineer the application). + +The general idea is as follows: + +* You write and execute a Python script, which produces a ``.c`` file + with the API of your choice (and optionally compile it into a + ``.so/.dll/.dylib``). The script also gives some Python code to be + "frozen" inside the ``.so``. + +* At runtime, the C application loads this ``.so/.dll/.dylib`` (or is + statically linked with the ``.c`` source) without having to know that + it was produced from Python and CFFI. + +* The first time a C function is called, Python is initialized and + the frozen Python code is executed. + +* The frozen Python code defines more Python functions that implement the + C functions of your API, which are then used for all subsequent C + function calls. + +One of the goals of this approach is to be entirely independent from +the CPython C API: no ``Py_Initialize()`` nor ``PyRun_SimpleString()`` +nor even ``PyObject``. It works identically on CPython and PyPy. + +This is entirely *new in version 1.5.* (PyPy contains CFFI 1.5 since +release 5.0.) + + +Usage +----- + +.. __: overview.html#embedding + +See the `paragraph in the overview page`__ for a quick introduction. +In this section, we explain every step in more details. We will use +here this slightly expanded example: + +.. code-block:: c + + /* file plugin.h */ + typedef struct { int x, y; } point_t; + extern int do_stuff(point_t *); + +.. code-block:: c + + /* file plugin.h, Windows-friendly version */ + typedef struct { int x, y; } point_t; + + /* When including this file from ffibuilder.set_source(), the + following macro is defined to '__declspec(dllexport)'. When + including this file directly from your C program, we define + it to 'extern __declspec(dllimport)' instead. + + With non-MSVC compilers we simply define it to 'extern'. + (The 'extern' is needed for sharing global variables; + functions would be fine without it. The macros always + include 'extern': you must not repeat it when using the + macros later.) + */ + #ifndef CFFI_DLLEXPORT + # if defined(_MSC_VER) + # define CFFI_DLLEXPORT extern __declspec(dllimport) + # else + # define CFFI_DLLEXPORT extern + # endif + #endif + + CFFI_DLLEXPORT int do_stuff(point_t *); + +.. code-block:: python + + # file plugin_build.py + import cffi + ffibuilder = cffi.FFI() + + with open('plugin.h') as f: + # read plugin.h and pass it to embedding_api(), manually + # removing the '#' directives and the CFFI_DLLEXPORT + data = ''.join([line for line in f if not line.startswith('#')]) + data = data.replace('CFFI_DLLEXPORT', '') + ffibuilder.embedding_api(data) + + ffibuilder.set_source("my_plugin", r''' + #include "plugin.h" + ''') + + ffibuilder.embedding_init_code(""" + from my_plugin import ffi + + @ffi.def_extern() + def do_stuff(p): + print("adding %d and %d" % (p.x, p.y)) + return p.x + p.y + """) + + ffibuilder.compile(target="plugin-1.5.*", verbose=True) + # or: ffibuilder.emit_c_code("my_plugin.c") + +Running the code above produces a *DLL*, i,e, a dynamically-loadable +library. It is a file with the extension ``.dll`` on Windows, +``.dylib`` on Mac OS/X, or ``.so`` on other platforms. As usual, it +is produced by generating some intermediate ``.c`` code and then +calling the regular platform-specific C compiler. See below__ for +some pointers to C-level issues with using the produced library. + +.. __: `Issues about using the .so`_ + +Here are some details about the methods used above: + +* **ffibuilder.embedding_api(source):** parses the given C source, which + declares functions that you want to be exported by the DLL. It can + also declare types, constants and global variables that are part of + the C-level API of your DLL. + + The functions that are found in ``source`` will be automatically + defined in the ``.c`` file: they will contain code that initializes + the Python interpreter the first time any of them is called, + followed by code to call the attached Python function (with + ``@ffi.def_extern()``, see next point). + + The global variables, on the other hand, are not automatically + produced. You have to write their definition explicitly in + ``ffibuilder.set_source()``, as regular C code (see the point after next). + +* **ffibuilder.embedding_init_code(python_code):** this gives + initialization-time Python source code. This code is copied + ("frozen") inside the DLL. At runtime, the code is executed when + the DLL is first initialized, just after Python itself is + initialized. This newly initialized Python interpreter has got an + extra "built-in" module that can be loaded magically without + accessing any files, with a line like "``from my_plugin import ffi, + lib``". The name ``my_plugin`` comes from the first argument to + ``ffibuilder.set_source()``. This module represents "the caller's C world" + from the point of view of Python. + + The initialization-time Python code can import other modules or + packages as usual. You may have typical Python issues like needing + to set up ``sys.path`` somehow manually first. + + For every function declared within ``ffibuilder.embedding_api()``, the + initialization-time Python code or one of the modules it imports + should use the decorator ``@ffi.def_extern()`` to attach a + corresponding Python function to it. + + If the initialization-time Python code fails with an exception, then + you get a traceback printed to stderr, along with more information + to help you identify problems like wrong ``sys.path``. If some + function remains unattached at the time where the C code tries to + call it, an error message is also printed to stderr and the function + returns zero/null. + + Note that the CFFI module never calls ``exit()``, but CPython itself + contains code that calls ``exit()``, for example if importing + ``site`` fails. This may be worked around in the future. + +* **ffibuilder.set_source(c_module_name, c_code):** set the name of the + module from Python's point of view. It also gives more C code which + will be included in the generated C code. In trivial examples it + can be an empty string. It is where you would ``#include`` some + other files, define global variables, and so on. The macro + ``CFFI_DLLEXPORT`` is available to this C code: it expands to the + platform-specific way of saying "the following declaration should be + exported from the DLL". For example, you would put "``extern int + my_glob;``" in ``ffibuilder.embedding_api()`` and "``CFFI_DLLEXPORT int + my_glob = 42;``" in ``ffibuilder.set_source()``. + + Currently, any *type* declared in ``ffibuilder.embedding_api()`` must also + be present in the ``c_code``. This is automatic if this code + contains a line like ``#include "plugin.h"`` in the example above. + +* **ffibuilder.compile([target=...] [, verbose=True]):** make the C code and + compile it. By default, it produces a file called + ``c_module_name.dll``, ``c_module_name.dylib`` or + ``c_module_name.so``, but the default can be changed with the + optional ``target`` keyword argument. You can use + ``target="foo.*"`` with a literal ``*`` to ask for a file called + ``foo.dll`` on Windows, ``foo.dylib`` on OS/X and ``foo.so`` + elsewhere. One reason for specifying an alternate ``target`` is to + include characters not usually allowed in Python module names, like + "``plugin-1.5.*``". + + For more complicated cases, you can call instead + ``ffibuilder.emit_c_code("foo.c")`` and compile the resulting ``foo.c`` + file using other means. CFFI's compilation logic is based on the + standard library ``distutils`` package, which is really developed + and tested for the purpose of making CPython extension modules; it + might not always be appropriate for making general DLLs. Also, just + getting the C code is what you need if you do not want to make a + stand-alone ``.so/.dll/.dylib`` file: this C file can be compiled + and statically linked as part of a larger application. + + +More reading +------------ + +If you're reading this page about embedding and you are not familiar +with CFFI already, here are a few pointers to what you could read +next: + +* For the ``@ffi.def_extern()`` functions, integer C types are passed + simply as Python integers; and simple pointers-to-struct and basic + arrays are all straightforward enough. However, sooner or later you + will need to read about this topic in more details here__. + +* ``@ffi.def_extern()``: see `documentation here,`__ notably on what + happens if the Python function raises an exception. + +* To create Python objects attached to C data, one common solution is + to use ``ffi.new_handle()``. See documentation here__. + +* In embedding mode, the major direction is C code that calls Python + functions. This is the opposite of the regular extending mode of + CFFI, in which the major direction is Python code calling C. That's + why the page `Using the ffi/lib objects`_ talks first about the + latter, and why the direction "C code that calls Python" is + generally referred to as "callbacks" in that page. If you also + need to have your Python code call C code, read more about + `Embedding and Extending`_ below. + +* ``ffibuilder.embedding_api(source)``: follows the same syntax as + ``ffibuilder.cdef()``, `documented here.`__ You can use the "``...``" + syntax as well, although in practice it may be less useful than it + is for ``cdef()``. On the other hand, it is expected that often the + C sources that you need to give to ``ffibuilder.embedding_api()`` would be + exactly the same as the content of some ``.h`` file that you want to + give to users of your DLL. That's why the example above does this:: + + with open('foo.h') as f: + ffibuilder.embedding_api(f.read()) + + Note that a drawback of this approach is that ``ffibuilder.embedding_api()`` + doesn't support ``#ifdef`` directives. You may have to use a more + convoluted expression like:: + + with open('foo.h') as f: + lines = [line for line in f if not line.startswith('#')] + ffibuilder.embedding_api(''.join(lines)) + + As in the example above, you can also use the same ``foo.h`` from + ``ffibuilder.set_source()``:: + + ffibuilder.set_source('module_name', r''' + #include "foo.h" + ''') + + +.. __: using.html#working +.. __: using.html#def-extern +.. __: ref.html#ffi-new-handle +.. __: cdef.html#cdef + +.. _`Using the ffi/lib objects`: using.html + + +Troubleshooting +--------------- + +* The error message + + cffi extension module 'c_module_name' has unknown version 0x2701 + + means that the running Python interpreter located a CFFI version older + than 1.5. CFFI 1.5 or newer must be installed in the running Python. + +* On PyPy, the error message + + debug: pypy_setup_home: directories 'lib-python' and 'lib_pypy' not + found in pypy's shared library location or in any parent directory + + means that the ``libpypy-c.so`` file was found, but the standard library + was not found from this location. This occurs at least on some Linux + distributions, because they put ``libpypy-c.so`` inside ``/usr/lib/``, + instead of the way we recommend, which is: keep that file inside + ``/opt/pypy/bin/`` and put a symlink to there from ``/usr/lib/``. + The quickest fix is to do that change manually. + + +Issues about using the .so +-------------------------- + +This paragraph describes issues that are not necessarily specific to +CFFI. It assumes that you have obtained the ``.so/.dylib/.dll`` file as +described above, but that you have troubles using it. (In summary: it +is a mess. This is my own experience, slowly built by using Google and +by listening to reports from various platforms. Please report any +inaccuracies in this paragraph or better ways to do things.) + +* The file produced by CFFI should follow this naming pattern: + ``libmy_plugin.so`` on Linux, ``libmy_plugin.dylib`` on Mac, or + ``my_plugin.dll`` on Windows (no ``lib`` prefix on Windows). + +* First note that this file does not contain the Python interpreter + nor the standard library of Python. You still need it to be + somewhere. There are ways to compact it to a smaller number of files, + but this is outside the scope of CFFI (please report if you used some + of these ways successfully so that I can add some links here). + +* In what we'll call the "main program", the ``.so`` can be either + used dynamically (e.g. by calling ``dlopen()`` or ``LoadLibrary()`` + inside the main program), or at compile-time (e.g. by compiling it + with ``gcc -lmy_plugin``). The former case is always used if you're + building a plugin for a program, and the program itself doesn't need + to be recompiled. The latter case is for making a CFFI library that + is more tightly integrated inside the main program. + +* In the case of compile-time usage: you can add the gcc + option ``-Lsome/path/`` before ``-lmy_plugin`` to describe where the + ``libmy_plugin.so`` is. On some platforms, notably Linux, ``gcc`` + will complain if it can find ``libmy_plugin.so`` but not + ``libpython27.so`` or ``libpypy-c.so``. To fix it, you need to call + ``LD_LIBRARY_PATH=/some/path/to/libpypy gcc``. + +* When actually executing the main program, it needs to find the + ``libmy_plugin.so`` but also ``libpython27.so`` or ``libpypy-c.so``. + For PyPy, unpack a PyPy distribution and you get a full directory + structure with ``libpypy-c.so`` inside a ``bin`` subdirectory, or on + Windows ``pypy-c.dll`` inside the top directory; you must not move + this file around, but just point to it. One way to point to it is by + running the main program with some environment variable: + ``LD_LIBRARY_PATH=/some/path/to/libpypy`` on Linux, + ``DYLD_LIBRARY_PATH=/some/path/to/libpypy`` on OS/X. + +* You can avoid the ``LD_LIBRARY_PATH`` issue if you compile + ``libmy_plugin.so`` with the path hard-coded inside in the first + place. On Linux, this is done by ``gcc -Wl,-rpath=/some/path``. You + would put this option in ``ffibuilder.set_source("my_plugin", ..., + extra_link_args=['-Wl,-rpath=/some/path/to/libpypy'])``. The path can + start with ``$ORIGIN`` to mean "the directory where + ``libmy_plugin.so`` is". You can then specify a path relative to that + place, like ``extra_link_args=['-Wl,-rpath=$ORIGIN/../venv/bin']``. + Use ``ldd libmy_plugin.so`` to look at what path is currently compiled + in after the expansion of ``$ORIGIN``.) + + After this, you don't need ``LD_LIBRARY_PATH`` any more to locate + ``libpython27.so`` or ``libpypy-c.so`` at runtime. In theory it + should also cover the call to ``gcc`` for the main program. I wasn't + able to make ``gcc`` happy without ``LD_LIBRARY_PATH`` on Linux if + the rpath starts with ``$ORIGIN``, though. + +* The same rpath trick might be used to let the main program find + ``libmy_plugin.so`` in the first place without ``LD_LIBRARY_PATH``. + (This doesn't apply if the main program uses ``dlopen()`` to load it + as a dynamic plugin.) You'd make the main program with ``gcc + -Wl,-rpath=/path/to/libmyplugin``, possibly with ``$ORIGIN``. The + ``$`` in ``$ORIGIN`` causes various shell problems on its own: if + using a common shell you need to say ``gcc + -Wl,-rpath=\$ORIGIN``. From a Makefile, you need to say + something like ``gcc -Wl,-rpath=\$$ORIGIN``. + +* On some Linux distributions, notably Debian, the ``.so`` files of + CPython C extension modules may be compiled without saying that they + depend on ``libpythonX.Y.so``. This makes such Python systems + unsuitable for embedding if the embedder uses ``dlopen(..., + RTLD_LOCAL)``. You get an ``undefined symbol`` error. See + `issue #264`__. A workaround is to first call + ``dlopen("libpythonX.Y.so", RTLD_LAZY|RTLD_GLOBAL)``, which will + force ``libpythonX.Y.so`` to be loaded first. + +.. __: https://bitbucket.org/cffi/cffi/issues/264/ + + +Using multiple CFFI-made DLLs +----------------------------- + +Multiple CFFI-made DLLs can be used by the same process. + +Note that all CFFI-made DLLs in a process share a single Python +interpreter. The effect is the same as the one you get by trying to +build a large Python application by assembling a lot of unrelated +packages. Some of these might be libraries that monkey-patch some +functions from the standard library, for example, which might be +unexpected from other parts. + + +Multithreading +-------------- + +Multithreading should work transparently, based on Python's standard +Global Interpreter Lock. + +If two threads both try to call a C function when Python is not yet +initialized, then locking occurs. One thread proceeds with +initialization and blocks the other thread. The other thread will be +allowed to continue only when the execution of the initialization-time +Python code is done. + +If the two threads call two *different* CFFI-made DLLs, the Python +initialization itself will still be serialized, but the two pieces of +initialization-time Python code will not. The idea is that there is a +priori no reason for one DLL to wait for initialization of the other +DLL to be complete. + +After initialization, Python's standard Global Interpreter Lock kicks +in. The end result is that when one CPU progresses on executing +Python code, no other CPU can progress on executing more Python code +from another thread of the same process. At regular intervals, the +lock switches to a different thread, so that no single thread should +appear to block indefinitely. + + +Testing +------- + +For testing purposes, a CFFI-made DLL can be imported in a running +Python interpreter instead of being loaded like a C shared library. + +You might have some issues with the file name: for example, on +Windows, Python expects the file to be called ``c_module_name.pyd``, +but the CFFI-made DLL is called ``target.dll`` instead. The base name +``target`` is the one specified in ``ffibuilder.compile()``, and on Windows +the extension is ``.dll`` instead of ``.pyd``. You have to rename or +copy the file, or on POSIX use a symlink. + +The module then works like a regular CFFI extension module. It is +imported with "``from c_module_name import ffi, lib``" and exposes on +the ``lib`` object all C functions. You can test it by calling these +C functions. The initialization-time Python code frozen inside the +DLL is executed the first time such a call is done. + + +Embedding and Extending +----------------------- + +The embedding mode is not incompatible with the non-embedding mode of +CFFI. + +You can use *both* ``ffibuilder.embedding_api()`` and +``ffibuilder.cdef()`` in the +same build script. You put in the former the declarations you want to +be exported by the DLL; you put in the latter only the C functions and +types that you want to share between C and Python, but not export from +the DLL. + +As an example of that, consider the case where you would like to have +a DLL-exported C function written in C directly, maybe to handle some +cases before calling Python functions. To do that, you must *not* put +the function's signature in ``ffibuilder.embedding_api()``. (Note that this +requires more hacks if you use ``ffibuilder.embedding_api(f.read())``.) +You must only write the custom function definition in +``ffibuilder.set_source()``, and prefix it with the macro CFFI_DLLEXPORT: + +.. code-block:: c + + CFFI_DLLEXPORT int myfunc(int a, int b) + { + /* implementation here */ + } + +This function can, if it wants, invoke Python functions using the +general mechanism of "callbacks"---called this way because it is a +call from C to Python, although in this case it is not calling +anything back: + +.. code-block:: python + + ffibuilder.cdef(""" + extern "Python" int mycb(int); + """) + + ffibuilder.set_source("my_plugin", r""" + + static int mycb(int); /* the callback: forward declaration, to make + it accessible from the C code that follows */ + + CFFI_DLLEXPORT int myfunc(int a, int b) + { + int product = a * b; /* some custom C code */ + return mycb(product); + } + """) + +and then the Python initialization code needs to contain the lines: + +.. code-block:: python + + @ffi.def_extern() + def mycb(x): + print "hi, I'm called with x =", x + return x * 10 + +This ``@ffi.def_extern`` is attaching a Python function to the C +callback ``mycb()``, which in this case is not exported from the DLL. +Nevertheless, the automatic initialization of Python occurs when +``mycb()`` is called, if it happens to be the first function called +from C. More precisely, it does not happen when ``myfunc()`` is +called: this is just a C function, with no extra code magically +inserted around it. It only happens when ``myfunc()`` calls +``mycb()``. + +As the above explanation hints, this is how ``ffibuilder.embedding_api()`` +actually implements function calls that directly invoke Python code; +here, we have merely decomposed it explicitly, in order to add some +custom C code in the middle. + +In case you need to force, from C code, Python to be initialized +before the first ``@ffi.def_extern()`` is called, you can do so by +calling the C function ``cffi_start_python()`` with no argument. It +returns an integer, 0 or -1, to tell if the initialization succeeded +or not. Currently there is no way to prevent a failing initialization +from also dumping a traceback and more information to stderr. diff --git a/doc/source/goals.rst b/doc/source/goals.rst new file mode 100644 index 0000000..0fda659 --- /dev/null +++ b/doc/source/goals.rst @@ -0,0 +1,69 @@ +Goals +----- + +The interface is based on `LuaJIT's FFI`_, and follows a few principles: + +* The goal is to call C code from Python without learning a 3rd language: + existing alternatives require users to learn domain specific language + (Cython_, SWIG_) or API (ctypes_). The CFFI design requires users to know + only C and Python, minimizing the extra bits of API that need to be learned. + +* Keep all the Python-related logic in Python so that you don't need to + write much C code (unlike `CPython native C extensions`_). + +* The preferred way is to work at the level of the API (Application + Programming Interface): the C compiler is called from the declarations + you write to validate and link to the C language constructs. + Alternatively, it is also possible to work at the ABI level + (Application Binary Interface), the way ctypes_ work. + However, on non-Windows platforms, C libraries typically + have a specified C API but not an ABI (e.g. they may + document a "struct" as having at least these fields, but maybe more). + +* Try to be complete. For now some C99 constructs are not supported, + but all C89 should be, including macros (and including macro "abuses", + which you can `manually wrap`_ in saner-looking C functions). + +* Attempt to support both PyPy and CPython, with a reasonable path + for other Python implementations like IronPython and Jython. + +* Note that this project is **not** about embedding executable C code in + Python, unlike `Weave`_. This is about calling existing C libraries + from Python. + +* There is no C++ support. Sometimes, it is reasonable to write a C + wrapper around the C++ code and then call this C API with CFFI. + Otherwise, look at other projects. I would recommend cppyy_, which + has got some similarities (and also works efficiently on both CPython + and PyPy). + +.. _`LuaJIT's FFI`: http://luajit.org/ext_ffi.html +.. _`Cython`: http://www.cython.org +.. _`SWIG`: http://www.swig.org/ +.. _`CPython native C extensions`: http://docs.python.org/extending/extending.html +.. _`native C extensions`: http://docs.python.org/extending/extending.html +.. _`ctypes`: http://docs.python.org/library/ctypes.html +.. _`Weave`: http://wiki.scipy.org/Weave +.. _`cppyy`: http://cppyy.readthedocs.io/en/latest/ +.. _`manually wrap`: overview.html#abi-versus-api + +Get started by reading `the overview`__. + +.. __: overview.html + + +Comments and bugs +----------------- + +The best way to contact us is on the IRC ``#pypy`` channel of +``irc.freenode.net``. Feel free to discuss matters either there or in +the `mailing list`_. Please report to the `issue tracker`_ any bugs. + +As a general rule, when there is a design issue to resolve, we pick the +solution that is the "most C-like". We hope that this module has got +everything you need to access C code and nothing more. + +--- the authors, Armin Rigo and Maciej Fijalkowski + +.. _`issue tracker`: https://bitbucket.org/cffi/cffi/issues +.. _`mailing list`: https://groups.google.com/forum/#!forum/python-cffi diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..1126318 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,22 @@ +================================ +CFFI documentation +================================ + +C Foreign Function Interface for Python. Interact with almost any C +code from Python, based on C-like declarations that you can often +copy-paste from header files or documentation. + +.. toctree:: + :maxdepth: 2 + + goals + whatsnew + installation + overview + using + ref + cdef + embedding + + + diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 0000000..4af17d5 --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,206 @@ +======================================================= +Installation and Status +======================================================= + +Quick installation for CPython (cffi is distributed with PyPy): + +* ``pip install cffi`` + +* or get the source code via the `Python Package Index`__. + +.. __: http://pypi.python.org/pypi/cffi + +In more details: + +This code has been developed on Linux, but should work on any POSIX +platform as well as on Windows 32 and 64. (It relies occasionally on +libffi, so it depends on libffi being bug-free; this may not be fully +the case on some of the more exotic platforms.) + +CFFI supports CPython 2.6, 2.7, 3.x (tested with 3.2 to 3.4); and is +distributed with PyPy (CFFI 1.0 is distributed with and requires +PyPy 2.6). + +The core speed of CFFI is better than ctypes, with import times being +either lower if you use the post-1.0 features, or much higher if you +don't. The wrapper Python code you typically need to write around the +raw CFFI interface slows things down on CPython, but not unreasonably +so. On PyPy, this wrapper code has a minimal impact thanks to the JIT +compiler. This makes CFFI the recommended way to interface with C +libraries on PyPy. + +Requirements: + +* CPython 2.6 or 2.7 or 3.x, or PyPy (PyPy 2.0 for the earliest + versions of CFFI; or PyPy 2.6 for CFFI 1.0). + +* in some cases you need to be able to compile C extension modules. + On non-Windows platforms, this usually means installing the package + ``python-dev``. Refer to the appropriate docs for your OS. + +* on CPython, on non-Windows platforms, you also need to install + ``libffi-dev`` in order to compile CFFI itself. + +* pycparser >= 2.06: https://github.com/eliben/pycparser (automatically + tracked by ``pip install cffi``). + +* `py.test`_ is needed to run the tests of CFFI itself. + +.. _`py.test`: http://pypi.python.org/pypi/pytest + +Download and Installation: + +* https://pypi.python.org/pypi/cffi + +* Checksums of the "source" package version 1.12.2: + + - MD5: 4d7dcb6c7c738c15d2ece9bd4c5f86da + + - SHA: 5f579d4980cbcc8aac592721f714ef6a64370ab1 + + - SHA256: e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 + +* Or grab the most current version from the `Bitbucket page`_: + ``hg clone https://bitbucket.org/cffi/cffi`` + +* ``python setup.py install`` or ``python setup_base.py install`` + (should work out of the box on Linux or Windows; see below for + `MacOS X`_ or `Windows 64`_.) + +* running the tests: ``py.test c/ testing/`` (if you didn't + install cffi yet, you need first ``python setup_base.py build_ext -f + -i``) + +.. _`Bitbucket page`: https://bitbucket.org/cffi/cffi + +Demos: + +* The `demo`_ directory contains a number of small and large demos + of using ``cffi``. + +* The documentation below might be sketchy on details; for now the + ultimate reference is given by the tests, notably + `testing/cffi1/test_verify1.py`_ and `testing/cffi0/backend_tests.py`_. + +.. _`demo`: https://bitbucket.org/cffi/cffi/src/default/demo +.. _`testing/cffi1/test_verify1.py`: https://bitbucket.org/cffi/cffi/src/default/testing/cffi1/test_verify1.py +.. _`testing/cffi0/backend_tests.py`: https://bitbucket.org/cffi/cffi/src/default/testing/cffi0/backend_tests.py + + +Platform-specific instructions +------------------------------ + +``libffi`` is notoriously messy to install and use --- to the point that +CPython includes its own copy to avoid relying on external packages. +CFFI does the same for Windows, but not for other platforms (which should +have their own working libffi's). +Modern Linuxes work out of the box thanks to ``pkg-config``. Here are some +(user-supplied) instructions for other platforms. + + +MacOS X ++++++++ + +**Homebrew** (Thanks David Griffin for this) + +1) Install homebrew: http://brew.sh + +2) Run the following commands in a terminal + +:: + + brew install pkg-config libffi + PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig pip install cffi + + +Alternatively, **on OS/X 10.6** (Thanks Juraj Sukop for this) + +For building libffi you can use the default install path, but then, in +``setup.py`` you need to change:: + + include_dirs = [] + +to:: + + include_dirs = ['/usr/local/lib/libffi-3.0.11/include'] + +Then running ``python setup.py build`` complains about "fatal error: error writing to -: Broken pipe", which can be fixed by running:: + + ARCHFLAGS="-arch i386 -arch x86_64" python setup.py build + +as described here_. + +.. _here: http://superuser.com/questions/259278/python-2-6-1-pycrypto-2-3-pypi-package-broken-pipe-during-build + + +Windows (regular 32-bit) +++++++++++++++++++++++++ + +Win32 works and is tested at least each official release. + +The recommended C compiler compatible with Python 2.7 is this one: +http://www.microsoft.com/en-us/download/details.aspx?id=44266 +There is a known problem with distutils on Python 2.7, as +explained in https://bugs.python.org/issue23246, and the same +problem applies whenever you want to run compile() to build a dll with +this specific compiler suite download. +``import setuptools`` might help, but YMMV + +For Python 3.4 and beyond: +https://www.visualstudio.com/en-us/downloads/visual-studio-2015-ctp-vs + + +Windows 64 +++++++++++ + +Win64 received very basic testing and we applied a few essential +fixes in cffi 0.7. The comment above applies for Python 2.7 on +Windows 64 as well. Please report any other issue. + +Note as usual that this is only about running the 64-bit version of +Python on the 64-bit OS. If you're running the 32-bit version (the +common case apparently), then you're running Win32 as far as we're +concerned. + +.. _`issue 9`: https://bitbucket.org/cffi/cffi/issue/9 +.. _`Python issue 7546`: http://bugs.python.org/issue7546 + + +Linux and OS/X: UCS2 versus UCS4 +++++++++++++++++++++++++++++++++ + +This is about getting an ImportError about ``_cffi_backend.so`` with a +message like ``Symbol not found: _PyUnicodeUCS2_AsASCIIString``. This +error occurs in Python 2 as soon as you mix "ucs2" and "ucs4" builds of +Python. It means that you are now running a Python compiled with +"ucs4", but the extension module ``_cffi_backend.so`` was compiled by a +different Python: one that was running "ucs2". (If the opposite problem +occurs, you get an error about ``_PyUnicodeUCS4_AsASCIIString`` +instead.) + +If you are using ``pyenv``, then see +https://github.com/yyuu/pyenv/issues/257. + +More generally, the solution that should always work is to download the +sources of CFFI (instead of a prebuilt binary) and make sure that you +build it with the same version of Python than the one that will use it. +For example, with virtualenv: + +* ``virtualenv ~/venv`` + +* ``cd ~/path/to/sources/of/cffi`` + +* ``~/venv/bin/python setup.py build --force`` # forcing a rebuild to + make sure + +* ``~/venv/bin/python setup.py install`` + +This will compile and install CFFI in this virtualenv, using the +Python from this virtualenv. + + +NetBSD +++++++ + +You need to make sure you have an up-to-date version of libffi, which +fixes some bugs. diff --git a/doc/source/overview.rst b/doc/source/overview.rst new file mode 100644 index 0000000..bcc5663 --- /dev/null +++ b/doc/source/overview.rst @@ -0,0 +1,651 @@ +======================================================= +Overview +======================================================= + +.. contents:: + + +The first section presents a simple working +example of using CFFI to call a C function in a compiled shared object +(DLL) from Python. CFFI is +flexible and covers several other use cases presented in the second +section. The third section shows how to export Python functions +to a Python interpreter embedded in a C or C++ application. The last +two sections delve deeper in the CFFI library. + +Make sure you have `cffi installed`__. + +.. __: installation.html + +.. _out-of-line-api-level: +.. _real-example: + + +Main mode of usage +------------------ + +The main way to use CFFI is as an interface to some already-compiled +shared object which is provided by other means. Imagine that you have a +system-installed shared object called ``piapprox.dll`` (Windows) or +``libpiapprox.so`` (Linux and others) or ``libpiapprox.dylib`` (OS X), +exporting a function ``float pi_approx(int n);`` that computes some +approximation of pi given a number of iterations. You want to call +this function from Python. Note this method works equally well with a +static library ``piapprox.lib`` (Windows) or ``libpiapprox.a``. + +Create the file ``piapprox_build.py``: + +.. code-block:: python + + from cffi import FFI + ffibuilder = FFI() + + # cdef() expects a single string declaring the C types, functions and + # globals needed to use the shared object. It must be in valid C syntax. + ffibuilder.cdef(""" + float pi_approx(int n); + """) + + # set_source() gives the name of the python extension module to + # produce, and some C source code as a string. This C code needs + # to make the declarated functions, types and globals available, + # so it is often just the "#include". + ffibuilder.set_source("_pi_cffi", + """ + #include "pi.h" // the C header of the library + """, + libraries=['piapprox']) # library name, for the linker + + if __name__ == "__main__": + ffibuilder.compile(verbose=True) + +Execute this script. If everything is OK, it should produce +``_pi_cffi.c``, and then invoke the compiler on it. The produced +``_pi_cffi.c`` contains a copy of the string given in ``set_source()``, +in this example the ``#include "pi.h"``. Afterwards, it contains glue code +for all the functions, types and globals declared in the ``cdef()`` above. + +At runtime, you use the extension module like this: + +.. code-block:: python + + from _pi_cffi import ffi, lib + print(lib.pi_approx(5000)) + +That's all! In the rest of this page, we describe some more advanced +examples and other CFFI modes. In particular, there is a complete +example `if you don't have an already-installed C library to call`_. + +For more information about the ``cdef()`` and ``set_source()`` methods +of the ``FFI`` class, see `Preparing and Distributing modules`__. + +.. __: cdef.html + +When your example works, a common alternative to running the build +script manually is to have it run as part of a ``setup.py``. Here is +an example using the Setuptools distribution: + +.. code-block:: python + + from setuptools import setup + + setup( + ... + setup_requires=["cffi>=1.0.0"], + cffi_modules=["piapprox_build:ffibuilder"], # "filename:global" + install_requires=["cffi>=1.0.0"], + ) + + +Other CFFI modes +---------------- + +CFFI can be used in one of four modes: "ABI" versus "API" level, +each with "in-line" or "out-of-line" preparation (or compilation). + +The **ABI mode** accesses libraries at the binary level, whereas the +faster **API mode** accesses them with a C compiler. We explain the +difference in more details below__. + +.. __: `abi-versus-api`_ + +In the **in-line mode,** everything is set up every time you import +your Python code. In the **out-of-line mode,** you have a separate +step of preparation (and possibly C compilation) that produces a +module which your main program can then import. + + +Simple example (ABI level, in-line) ++++++++++++++++++++++++++++++++++++ + +May look familiar to those who have used ctypes_. + +.. code-block:: python + + >>> from cffi import FFI + >>> ffi = FFI() + >>> ffi.cdef(""" + ... int printf(const char *format, ...); // copy-pasted from the man page + ... """) + >>> C = ffi.dlopen(None) # loads the entire C namespace + >>> arg = ffi.new("char[]", b"world") # equivalent to C code: char arg[] = "world"; + >>> C.printf(b"hi there, %s.\n", arg) # call printf + hi there, world. + 17 # this is the return value + >>> + +Note that ``char *`` arguments expect a ``bytes`` object. If you have a +``str`` (or a ``unicode`` on Python 2) you need to encode it explicitly +with ``somestring.encode(myencoding)``. + +*Python 3 on Windows:* ``ffi.dlopen(None)`` does not work. This problem +is messy and not really fixable. The problem does not occur if you try +to call a function from a specific DLL that exists on your system: then +you use ``ffi.dlopen("path.dll")``. + +*This example does not call any C compiler. It works in the so-called +ABI mode, which means that it will crash if you call some function or +access some fields of a structure that was slightly misdeclared in the +cdef().* + +If using a C compiler to install your module is an option, it is highly +recommended to use the API mode instead. (It is also faster.) + + +Struct/Array Example (minimal, in-line) ++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: python + + from cffi import FFI + ffi = FFI() + ffi.cdef(""" + typedef struct { + unsigned char r, g, b; + } pixel_t; + """) + image = ffi.new("pixel_t[]", 800*600) + + f = open('data', 'rb') # binary mode -- important + f.readinto(ffi.buffer(image)) + f.close() + + image[100].r = 255 + image[100].g = 192 + image[100].b = 128 + + f = open('data', 'wb') + f.write(ffi.buffer(image)) + f.close() + +This can be used as a more flexible replacement of the struct_ and +array_ modules, and replaces ctypes_. You could also call ``ffi.new("pixel_t[600][800]")`` +and get a two-dimensional array. + +.. _struct: http://docs.python.org/library/struct.html +.. _array: http://docs.python.org/library/array.html +.. _ctypes: http://docs.python.org/library/ctypes.html + +*This example does not call any C compiler.* + +This example also admits an out-of-line equivalent. It is similar to +the first example `Main mode of usage`_ above, +but passing ``None`` as the second argument to +``ffibuilder.set_source()``. Then in the main program you write +``from _simple_example import ffi`` and then the same content as the +in-line example above starting from the line ``image = +ffi.new("pixel_t[]", 800*600)``. + + +API Mode, calling the C standard library +++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: python + + # file "example_build.py" + + # Note: we instantiate the same 'cffi.FFI' class as in the previous + # example, but call the result 'ffibuilder' now instead of 'ffi'; + # this is to avoid confusion with the other 'ffi' object you get below + + from cffi import FFI + ffibuilder = FFI() + + ffibuilder.set_source("_example", + r""" // passed to the real C compiler, + // contains implementation of things declared in cdef() + #include + #include + + // We can also define custom wrappers or other functions + // here (this is an example only): + static struct passwd *get_pw_for_root(void) { + return getpwuid(0); + } + """, + libraries=[]) # or a list of libraries to link with + # (more arguments like setup.py's Extension class: + # include_dirs=[..], extra_objects=[..], and so on) + + ffibuilder.cdef(""" + // declarations that are shared between Python and C + struct passwd { + char *pw_name; + ...; // literally dot-dot-dot + }; + struct passwd *getpwuid(int uid); // defined in + struct passwd *get_pw_for_root(void); // defined in set_source() + """) + + if __name__ == "__main__": + ffibuilder.compile(verbose=True) + +You need to run the ``example_build.py`` script once to generate +"source code" into the file ``_example.c`` and compile this to a +regular C extension module. (CFFI selects either Python or C for the +module to generate based on whether the second argument to +``set_source()`` is ``None`` or not.) + +*You need a C compiler for this single step. It produces a file called +e.g. _example.so or _example.pyd. If needed, it can be distributed in +precompiled form like any other extension module.* + +Then, in your main program, you use: + +.. code-block:: python + + from _example import ffi, lib + + p = lib.getpwuid(0) + assert ffi.string(p.pw_name) == b'root' + p = lib.get_pw_for_root() + assert ffi.string(p.pw_name) == b'root' + +Note that this works independently of the exact C layout of ``struct +passwd`` (it is "API level", as opposed to "ABI level"). It requires +a C compiler in order to run ``example_build.py``, but it is much more +portable than trying to get the details of the fields of ``struct +passwd`` exactly right. Similarly, in the ``cdef()`` we declared +``getpwuid()`` as taking an ``int`` argument; on some platforms this +might be slightly incorrect---but it does not matter. + +Note also that at runtime, the API mode is faster than the ABI mode. + +To integrate it inside a ``setup.py`` distribution with Setuptools: + +.. code-block:: python + + from setuptools import setup + + setup( + ... + setup_requires=["cffi>=1.0.0"], + cffi_modules=["example_build.py:ffibuilder"], + install_requires=["cffi>=1.0.0"], + ) + + +.. _`if you don't have an already-installed C library to call`: + +API Mode, calling C sources instead of a compiled library ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you want to call some library that is not precompiled, but for which +you have C sources, then the easiest solution is to make a single +extension module that is compiled from both the C sources of this +library, and the additional CFFI wrappers. For example, say you start +with the files ``pi.c`` and ``pi.h``: + + .. code-block:: C + + /* filename: pi.c*/ + # include + # include + + /* Returns a very crude approximation of Pi + given a int: a number of iteration */ + float pi_approx(int n){ + + double i,x,y,sum=0; + + for(i=0;i=1.0.0"], + cffi_modules=["simple_example_build.py:ffibuilder"], + install_requires=["cffi>=1.0.0"], + ) + +In summary, this mode is useful when you wish to declare many C structures but +do not need fast interaction with a shared object. It is useful for parsing +binary files, for instance. + + +In-line, API level +++++++++++++++++++ + +The "API level + in-line" mode combination exists but is long +deprecated. It used to be done with ``lib = ffi.verify("C header")``. +The out-of-line variant with ``set_source("modname", "C header")`` is +preferred and avoids a number of problems when the project grows in +size. + + +.. _embedding: + +Embedding +--------- + +*New in version 1.5.* + +CFFI can be used for embedding__: creating a standard +dynamically-linked library (``.dll`` under Windows, ``.so`` elsewhere) +which can be used from a C application. + +.. code-block:: python + + import cffi + ffibuilder = cffi.FFI() + + ffibuilder.embedding_api(""" + int do_stuff(int, int); + """) + + ffibuilder.set_source("my_plugin", "") + + ffibuilder.embedding_init_code(""" + from my_plugin import ffi + + @ffi.def_extern() + def do_stuff(x, y): + print("adding %d and %d" % (x, y)) + return x + y + """) + + ffibuilder.compile(target="plugin-1.5.*", verbose=True) + +This simple example creates ``plugin-1.5.dll`` or ``plugin-1.5.so`` as +a DLL with a single exported function, ``do_stuff()``. You execute +the script above once, with the interpreter you want to have +internally used; it can be CPython 2.x or 3.x or PyPy. This DLL can +then be used "as usual" from an application; the application doesn't +need to know that it is talking with a library made with Python and +CFFI. At runtime, when the application calls ``int do_stuff(int, +int)``, the Python interpreter is automatically initialized and ``def +do_stuff(x, y):`` gets called. `See the details in the documentation +about embedding.`__ + +.. __: embedding.html +.. __: embedding.html + + +What actually happened? +----------------------- + +The CFFI interface operates on the same level as C - you declare types +and functions using the same syntax as you would define them in C. This +means that most of the documentation or examples can be copied straight +from the man pages. + +The declarations can contain **types, functions, constants** +and **global variables.** What you pass to the ``cdef()`` must not +contain more than that; in particular, ``#ifdef`` or ``#include`` +directives are not supported. The cdef in the above examples are just +that - they declared "there is a function in the C level with this +given signature", or "there is a struct type with this shape". + +In the ABI examples, the ``dlopen()`` calls load libraries manually. +At the binary level, a program is split into multiple namespaces---a +global one (on some platforms), plus one namespace per library. So +``dlopen()`` returns a ```` object, and this object has +got as attributes all function, constant and variable symbols that are +coming from this library and that have been declared in the +``cdef()``. If you have several interdependent libraries to load, +you would call ``cdef()`` only once but ``dlopen()`` several times. + +By opposition, the API mode works more closely like a C program: the C +linker (static or dynamic) is responsible for finding any symbol used. +You name the libraries in the ``libraries`` keyword argument to +``set_source()``, but never need to say which symbol comes +from which library. +Other common arguments to ``set_source()`` include ``library_dirs`` and +``include_dirs``; all these arguments are passed to the standard +distutils/setuptools. + +The ``ffi.new()`` lines allocate C objects. They are filled +with zeroes initially, unless the optional second argument is used. +If specified, this argument gives an "initializer", like you can use +with C code to initialize global variables. + +The actual ``lib.*()`` function calls should be obvious: it's like C. + + +.. _abi-versus-api: + +ABI versus API +-------------- + +Accessing the C library at the binary level ("ABI") is fraught +with problems, particularly on non-Windows platforms. + +The most immediate drawback of the ABI level is that calling functions +needs to go through the very general *libffi* library, which is slow +(and not always perfectly tested on non-standard platforms). The API +mode instead compiles a CPython C wrapper that directly invokes the +target function. It can be massively faster (and works +better than libffi ever will). + +The more fundamental reason to prefer the API mode is that *the C +libraries are typically meant to be used with a C compiler.* You are not +supposed to do things like guess where fields are in the structures. +The "real example" above shows how CFFI uses a C compiler under the +hood: this example uses ``set_source(..., "C source...")`` and never +``dlopen()``. When using this approach, +we have the advantage that we can use literally "``...``" at various places in +the ``cdef()``, and the missing information will be completed with the +help of the C compiler. CFFI will turn this into a single C source file, +which contains the "C source" part unmodified, followed by some +"magic" C code and declarations derived from the ``cdef()``. When +this C file is compiled, the resulting C extension module will contain +all the information we need---or the C compiler will give warnings or +errors, as usual e.g. if we misdeclare some function's signature. + +Note that the "C source" part from ``set_source()`` can contain +arbitrary C code. You can use this to declare some +more helper functions written in C. To export +these helpers to Python, put their signature in the ``cdef()`` too. +(You can use the ``static`` C keyword in the "C source" part, +as in ``static int myhelper(int x) { return x * 42; }``, +because these helpers are only +referenced from the "magic" C code that is generated afterwards in the +same C file.) + +This can be used for example to wrap "crazy" macros into more standard +C functions. The extra layer of C can be useful for other reasons +too, like calling functions that expect some complicated argument +structures that you prefer to build in C rather than in Python. (On +the other hand, if all you need is to call "function-like" macros, +then you can directly declare them in the ``cdef()`` as if they were +functions.) + +The generated piece of C code should be the same independently on the +platform on which you run it (or the Python version), so in simple cases +you can directly distribute the pre-generated C code and treat it as a +regular C extension module (which depends on the ``_cffi_backend`` +module, on CPython). The special Setuptools lines in the `example +above`__ are meant for the more complicated cases where we need to +regenerate the C sources as well---e.g. because the Python script that +regenerates this file will itself look around the system to know what it +should include or not. + +.. __: real-example_ diff --git a/doc/source/ref.rst b/doc/source/ref.rst new file mode 100644 index 0000000..3dc8e4b --- /dev/null +++ b/doc/source/ref.rst @@ -0,0 +1,1007 @@ +================================ +CFFI Reference +================================ + +.. contents:: + + +FFI Interface +------------- + +*This page documents the runtime interface of the two types "FFI" and +"CompiledFFI". These two types are very similar to each other. You get +a CompiledFFI object if you import an out-of-line module. You get a FFI +object from explicitly writing cffi.FFI(). Unlike CompiledFFI, the type +FFI has also got additional methods documented on the* `next page`__. + +.. __: cdef.html + + +ffi.NULL +++++++++ + +**ffi.NULL**: a constant NULL of type ````. + + +ffi.error ++++++++++ + +**ffi.error**: the Python exception raised in various cases. (Don't +confuse it with ``ffi.errno``.) + + +ffi.new() ++++++++++ + +**ffi.new(cdecl, init=None)**: +allocate an instance according to the specified C type and return a +pointer to it. The specified C type must be either a pointer or an +array: ``new('X *')`` allocates an X and returns a pointer to it, +whereas ``new('X[n]')`` allocates an array of n X'es and returns an +array referencing it (which works mostly like a pointer, like in C). +You can also use ``new('X[]', n)`` to allocate an array of a +non-constant length n. See the `detailed documentation`__ for other +valid initializers. + +.. __: using.html#working + +When the returned ```` object goes out of scope, the memory is +freed. In other words the returned ```` object has ownership of +the value of type ``cdecl`` that it points to. This means that the raw +data can be used as long as this object is kept alive, but must not be +used for a longer time. Be careful about that when copying the +pointer to the memory somewhere else, e.g. into another structure. +Also, this means that a line like ``x = ffi.new(...)[0]`` is *always +wrong:* the newly allocated object goes out of scope instantly, and so +is freed immediately, and ``x`` is garbage. + +The returned memory is initially cleared (filled with zeroes), before +the optional initializer is applied. For performance, see +`ffi.new_allocator()`_ for a way to allocate non-zero-initialized +memory. + +*New in version 1.12:* see also ``ffi.release()``. + + +ffi.cast() +++++++++++ + +**ffi.cast("C type", value)**: similar to a C cast: returns an +instance of the named C type initialized with the given value. The +value is casted between integers or pointers of any type. + + +.. _ffi-errno: +.. _ffi-getwinerror: + +ffi.errno, ffi.getwinerror() +++++++++++++++++++++++++++++ + +**ffi.errno**: the value of ``errno`` received from the most recent C call +in this thread, and passed to the following C call. (This is a thread-local +read-write property.) + +**ffi.getwinerror(code=-1)**: on Windows, in addition to ``errno`` we +also save and restore the ``GetLastError()`` value across function +calls. This function returns this error code as a tuple ``(code, +message)``, adding a readable message like Python does when raising +WindowsError. If the argument ``code`` is given, format that code into +a message instead of using ``GetLastError()``. +(Note that it is also possible to declare and call the ``GetLastError()`` +function as usual.) + + +.. _ffi-string: +.. _ffi-unpack: + +ffi.string(), ffi.unpack() +++++++++++++++++++++++++++ + +**ffi.string(cdata, [maxlen])**: return a Python string (or unicode +string) from the 'cdata'. + +- If 'cdata' is a pointer or array of characters or bytes, returns the + null-terminated string. The returned string extends until the first + null character. The 'maxlen' argument limits how far we look for a + null character. If 'cdata' is an + array then 'maxlen' defaults to its length. See ``ffi.unpack()`` below + for a way to continue past the first null character. *Python 3:* this + returns a ``bytes``, not a ``str``. + +- If 'cdata' is a pointer or array of wchar_t, returns a unicode string + following the same rules. *New in version 1.11:* can also be + char16_t or char32_t. + +- If 'cdata' is a single character or byte or a wchar_t or charN_t, + returns it as a byte string or unicode string. (Note that in some + situation a single wchar_t or char32_t may require a Python unicode + string of length 2.) + +- If 'cdata' is an enum, returns the value of the enumerator as a string. + If the value is out of range, it is simply returned as the stringified + integer. + +**ffi.unpack(cdata, length)**: unpacks an array of C data of the given +length, returning a Python string/unicode/list. The 'cdata' should be +a pointer; if it is an array it is first converted to the pointer +type. *New in version 1.6.* + +- If 'cdata' is a pointer to 'char', returns a byte string. It does + not stop at the first null. (An equivalent way to do that is + ``ffi.buffer(cdata, length)[:]``.) + +- If 'cdata' is a pointer to 'wchar_t', returns a unicode string. + ('length' is measured in number of wchar_t; it is not the size in + bytes.) *New in version 1.11:* can also be char16_t or char32_t. + +- If 'cdata' is a pointer to anything else, returns a list, of the + given 'length'. (A slower way to do that is ``[cdata[i] for i in + range(length)]``.) + + +.. _ffi-buffer: +.. _ffi-from-buffer: + +ffi.buffer(), ffi.from_buffer() ++++++++++++++++++++++++++++++++ + +**ffi.buffer(cdata, [size])**: return a buffer object that references +the raw C data pointed to by the given 'cdata', of 'size' bytes. What +Python calls "a buffer", or more precisely "an object supporting the +buffer interface", is an object that represents some raw memory and +that can be passed around to various built-in or extension functions; +these built-in functions read from or write to the raw memory directly, +without needing an extra copy. + +The 'cdata' argument +must be a pointer or an array. If unspecified, the size of the +buffer is either the size of what ``cdata`` points to, or the whole size +of the array. + +Here are a few examples of where buffer() would be useful: + +- use ``file.write()`` and ``file.readinto()`` with + such a buffer (for files opened in binary mode) + +- overwrite the content of a struct: if ``p`` is a cdata pointing to + it, use ``ffi.buffer(p)[:] = newcontent``, where ``newcontent`` is + a bytes object (``str`` in Python 2). + +Remember that like in C, you can use ``array + index`` to get the pointer +to the index'th item of an array. (In C you might more naturally write +``&array[index]``, but that is equivalent.) + +The returned object's type is not the builtin ``buffer`` nor ``memoryview`` +types, because these types' API changes too much across Python versions. +Instead it has the following Python API (a subset of Python 2's ``buffer``) +in addition to supporting the buffer interface: + +- ``buf[:]`` or ``bytes(buf)``: copy data out of the buffer, returning a + regular byte string (or ``buf[start:end]`` for a part) + +- ``buf[:] = newstr``: copy data into the buffer (or ``buf[start:end] + = newstr``) + +- ``len(buf)``, ``buf[index]``, ``buf[index] = newchar``: access as a sequence + of characters. + +The buffer object returned by ``ffi.buffer(cdata)`` keeps alive the +``cdata`` object: if it was originally an owning cdata, then its +owned memory will not be freed as long as the buffer is alive. + +Python 2/3 compatibility note: you should avoid using ``str(buf)``, +because it gives inconsistent results between Python 2 and Python 3. +(This is similar to how ``str()`` gives inconsistent results on regular +byte strings). Use ``buf[:]`` instead. + +*New in version 1.10:* ``ffi.buffer`` is now the type of the returned +buffer objects; ``ffi.buffer()`` actually calls the constructor. + +**ffi.from_buffer([cdecl,] python_buffer, require_writable=False)**: +return an array cdata (by default a ````) that +points to the data of the given Python object, which must support the +buffer interface. Note that ``ffi.from_buffer()`` turns a generic +Python buffer object into a cdata object, whereas ``ffi.buffer()`` does +the opposite conversion. Both calls don't actually copy any data. + +``ffi.from_buffer()`` is meant to be used on objects +containing large quantities of raw data, like bytearrays +or ``array.array`` or numpy +arrays. It supports both the old *buffer* API (in Python 2.x) and the +new *memoryview* API. Note that if you pass a read-only buffer object, +you still get a regular ````; it is your responsibility +not to write there if the original buffer doesn't expect you to. +*In particular, never modify byte strings!* + +The original object is kept alive (and, in case +of memoryview, locked) as long as the cdata object returned by +``ffi.from_buffer()`` is alive. + +A common use case is calling a C function with some ``char *`` that +points to the internal buffer of a Python object; for this case you +can directly pass ``ffi.from_buffer(python_buffer)`` as argument to +the call. + +*New in version 1.10:* the ``python_buffer`` can be anything supporting +the buffer/memoryview interface (except unicode strings). Previously, +bytearray objects were supported in version 1.7 onwards (careful, if you +resize the bytearray, the ```` object will point to freed +memory); and byte strings were supported in version 1.8 onwards. + +*New in version 1.12:* added the optional *first* argument ``cdecl``, and +the keyword argument ``require_writable``: + +* ``cdecl`` defaults to ``"char[]"``, but a different array type can be + specified for the result. A value like ``"int[]"`` will return an array of + ints instead of chars, and its length will be set to the number of ints + that fit in the buffer (rounded down if the division is not exact). Values + like ``"int[42]"`` or ``"int[2][3]"`` will return an array of exactly 42 + (resp. 2-by-3) ints, raising a ValueError if the buffer is too small. The + difference between specifying ``"int[]"`` and using the older code ``p1 = + ffi.from_buffer(x); p2 = ffi.cast("int *", p1)`` is that the older code + needs to keep ``p1`` alive as long as ``p2`` is in use, because only ``p1`` + keeps the underlying Python object alive and locked. (In addition, + ``ffi.from_buffer("int[]", x)`` gives better array bound checking.) + +* if ``require_writable`` is set to True, the function fails if the buffer + obtained from ``python_buffer`` is read-only (e.g. if ``python_buffer`` is + a byte string). The exact exception is raised by the object itself, and + for things like bytes it varies with the Python version, so don't rely on + it. (Before version 1.12, the same effect can be achieved with a hack: + call ``ffi.memmove(python_buffer, b"", 0)``. This has no effect if the + object is writable, but fails if it is read-only.) Please keep in mind + that CFFI does not implement the C keyword ``const``: even if you set + ``require_writable`` to False explicitly, you still get a regular + read-write cdata pointer. + +*New in version 1.12:* see also ``ffi.release()``. + + +ffi.memmove() ++++++++++++++ + +**ffi.memmove(dest, src, n)**: copy ``n`` bytes from memory area +``src`` to memory area ``dest``. See examples below. Inspired by the +C functions ``memcpy()`` and ``memmove()``---like the latter, the +areas can overlap. Each of ``dest`` and ``src`` can be either a cdata +pointer or a Python object supporting the buffer/memoryview interface. +In the case of ``dest``, the buffer/memoryview must be writable. +*New in version 1.3.* Examples: + +* ``ffi.memmove(myptr, b"hello", 5)`` copies the 5 bytes of + ``b"hello"`` to the area that ``myptr`` points to. + +* ``ba = bytearray(100); ffi.memmove(ba, myptr, 100)`` copies 100 + bytes from ``myptr`` into the bytearray ``ba``. + +* ``ffi.memmove(myptr + 1, myptr, 100)`` shifts 100 bytes from + the memory at ``myptr`` to the memory at ``myptr + 1``. + +In versions before 1.10, ``ffi.from_buffer()`` had restrictions on the +type of buffer, which made ``ffi.memmove()`` more general. + +.. _ffi-typeof: +.. _ffi-sizeof: +.. _ffi-alignof: + +ffi.typeof(), ffi.sizeof(), ffi.alignof() ++++++++++++++++++++++++++++++++++++++++++ + +**ffi.typeof("C type" or cdata object)**: return an object of type +```` corresponding to the parsed string, or to the C type of the +cdata instance. Usually you don't need to call this function or to +explicitly manipulate ```` objects in your code: any place that +accepts a C type can receive either a string or a pre-parsed ``ctype`` +object (and because of caching of the string, there is no real +performance difference). It can still be useful in writing typechecks, +e.g.: + +.. code-block:: python + + def myfunction(ptr): + assert ffi.typeof(ptr) is ffi.typeof("foo_t*") + ... + +Note also that the mapping from strings like ``"foo_t*"`` to the +```` objects is stored in some internal dictionary. This +guarantees that there is only one ```` object, so you +can use the ``is`` operator to compare it. The downside is that the +dictionary entries are immortal for now. In the future, we may add +transparent reclamation of old, unused entries. In the meantime, note +that using strings like ``"int[%d]" % length`` to name a type will +create many immortal cached entries if called with many different +lengths. + +**ffi.sizeof("C type" or cdata object)**: return the size of the +argument in bytes. The argument can be either a C type, or a cdata object, +like in the equivalent ``sizeof`` operator in C. + +For ``array = ffi.new("T[]", n)``, then ``ffi.sizeof(array)`` returns +``n * ffi.sizeof("T")``. *New in version 1.9:* Similar rules apply for +structures with a variable-sized array at the end. More precisely, if +``p`` was returned by ``ffi.new("struct foo *", ...)``, then +``ffi.sizeof(p[0])`` now returns the total allocated size. In previous +versions, it used to just return ``ffi.sizeof(ffi.typeof(p[0]))``, which +is the size of the structure ignoring the variable-sized part. (Note +that due to alignment, it is possible for ``ffi.sizeof(p[0])`` to return +a value smaller than ``ffi.sizeof(ffi.typeof(p[0]))``.) + +**ffi.alignof("C type")**: return the natural alignment size in bytes of +the argument. Corresponds to the ``__alignof__`` operator in GCC. + + +.. _ffi-offsetof: +.. _ffi-addressof: + +ffi.offsetof(), ffi.addressof() ++++++++++++++++++++++++++++++++ + +**ffi.offsetof("C struct or array type", \*fields_or_indexes)**: return the +offset within the struct of the given field. Corresponds to ``offsetof()`` +in C. + +You can give several field names in case of nested structures. You +can also give numeric values which correspond to array items, in case +of a pointer or array type. For example, ``ffi.offsetof("int[5]", 2)`` +is equal to the size of two integers, as is ``ffi.offsetof("int *", 2)``. + + +**ffi.addressof(cdata, \*fields_or_indexes)**: limited equivalent to +the '&' operator in C: + +1. ``ffi.addressof()`` returns a cdata that +is a pointer to this struct or union. The returned pointer is only +valid as long as the original ``cdata`` object is; be sure to keep it +alive if it was obtained directly from ``ffi.new()``. + +2. ``ffi.addressof(, field-or-index...)`` returns the address +of a field or array item inside the given structure or array. In case +of nested structures or arrays, you can give more than one field or +index to look recursively. Note that ``ffi.addressof(array, index)`` +can also be expressed as ``array + index``: this is true both in CFFI +and in C, where ``&array[index]`` is just ``array + index``. + +3. ``ffi.addressof(, "name")`` returns the address of the +named function or global variable from the given library object. +For functions, it returns a regular cdata +object containing a pointer to the function. + +Note that the case 1. cannot be used to take the address of a +primitive or pointer, but only a struct or union. It would be +difficult to implement because only structs and unions are internally +stored as an indirect pointer to the data. If you need a C int whose +address can be taken, use ``ffi.new("int[1]")`` in the first place; +similarly, for a pointer, use ``ffi.new("foo_t *[1]")``. + + +.. _ffi-cdata: +.. _ffi-ctype: + +ffi.CData, ffi.CType +++++++++++++++++++++ + +**ffi.CData, ffi.CType**: the Python type of the objects referred to +as ```` and ```` in the rest of this document. Note +that some cdata objects may be actually of a subclass of +``ffi.CData``, and similarly with ctype, so you should check with +``if isinstance(x, ffi.CData)``. Also, ```` objects have +a number of attributes for introspection: ``kind`` and ``cname`` are +always present, and depending on the kind they may also have +``item``, ``length``, ``fields``, ``args``, ``result``, ``ellipsis``, +``abi``, ``elements`` and ``relements``. + +*New in version 1.10:* ``ffi.buffer`` is now `a type`__ as well. + +.. __: #ffi-buffer + + +.. _ffi-gc: + +ffi.gc() +++++++++ + +**ffi.gc(cdata, destructor, size=0)**: +return a new cdata object that points to the +same data. Later, when this new cdata object is garbage-collected, +``destructor(old_cdata_object)`` will be called. Example of usage: +``ptr = ffi.gc(lib.custom_malloc(42), lib.custom_free)``. +Note that like objects +returned by ``ffi.new()``, the returned pointer objects have *ownership*, +which means the destructor is called as soon as *this* exact returned +object is garbage-collected. + +*New in version 1.12:* see also ``ffi.release()``. + +**ffi.gc(ptr, None, size=0)**: +removes the ownership on a object returned by a +regular call to ``ffi.gc``, and no destructor will be called when it +is garbage-collected. The object is modified in-place, and the +function returns ``None``. *New in version 1.7: ffi.gc(ptr, None)* + +Note that ``ffi.gc()`` should be avoided for limited resources, or (with +cffi below 1.11) for large memory allocations. This is particularly +true on PyPy: its GC does not know how much memory or how many resources +the returned ``ptr`` holds. It will only run its GC when enough memory +it knows about has been allocated (and thus run the destructor possibly +later than you would expect). Moreover, the destructor is called in +whatever thread PyPy is at that moment, which might be a problem for +some C libraries. In these cases, consider writing a wrapper class with +custom ``__enter__()`` and ``__exit__()`` methods, allocating and +freeing the C data at known points in time, and using it in a ``with`` +statement. In cffi 1.12, see also ``ffi.release()``. + +*New in version 1.11:* the ``size`` argument. If given, this should be +an estimate of the size (in bytes) that ``ptr`` keeps alive. This +information is passed on to the garbage collector, fixing part of the +problem described above. The ``size`` argument is most important on +PyPy; on CPython, it is ignored so far, but in the future it could be +used to trigger more eagerly the cyclic reference GC, too (see CPython +`issue 31105`__). + +The form ``ffi.gc(ptr, None, size=0)`` can be called with a negative +``size``, to cancel the estimate. It is not mandatory, though: +nothing gets out of sync if the size estimates do not match. It only +makes the next GC start more or less early. + +Note that if you have several ``ffi.gc()`` objects, the corresponding +destructors will be called in a random order. If you need a particular +order, see the discussion in `issue 340`__. + +.. __: http://bugs.python.org/issue31105 +.. __: https://bitbucket.org/cffi/cffi/issues/340/resources-release-issues + + +.. _ffi-new-handle: +.. _ffi-from-handle: + +ffi.new_handle(), ffi.from_handle() ++++++++++++++++++++++++++++++++++++ + +**ffi.new_handle(python_object)**: return a non-NULL cdata of type +``void *`` that contains an opaque reference to ``python_object``. You +can pass it around to C functions or store it into C structures. Later, +you can use **ffi.from_handle(p)** to retrieve the original +``python_object`` from a value with the same ``void *`` pointer. +*Calling ffi.from_handle(p) is invalid and will likely crash if +the cdata object returned by new_handle() is not kept alive!* + +See a `typical usage example`_ below. + +(In case you are wondering, this ``void *`` is not the ``PyObject *`` +pointer. This wouldn't make sense on PyPy anyway.) + +The ``ffi.new_handle()/from_handle()`` functions *conceptually* work +like this: + +* ``new_handle()`` returns cdata objects that contains references to + the Python objects; we call them collectively the "handle" cdata + objects. The ``void *`` value in these handle cdata objects are + random but unique. + +* ``from_handle(p)`` searches all live "handle" cdata objects for the + one that has the same value ``p`` as its ``void *`` value. It then + returns the Python object referenced by that handle cdata object. + If none is found, you get "undefined behavior" (i.e. crashes). + +The "handle" cdata object keeps the Python object alive, similar to +how ``ffi.new()`` returns a cdata object that keeps a piece of memory +alive. If the handle cdata object *itself* is not alive any more, +then the association ``void * -> python_object`` is dead and +``from_handle()`` will crash. + +*New in version 1.4:* two calls to ``new_handle(x)`` are guaranteed to +return cdata objects with different ``void *`` values, even with the +same ``x``. This is a useful feature that avoids issues with unexpected +duplicates in the following trick: if you need to keep alive the +"handle" until explicitly asked to free it, but don't have a natural +Python-side place to attach it to, then the easiest is to ``add()`` it +to a global set. It can later be removed from the set by +``global_set.discard(p)``, with ``p`` any cdata object whose ``void *`` +value compares equal. + +.. _`typical usage example`: + +Usage example: suppose you have a C library where you must call a +``lib.process_document()`` function which invokes some callback. The +``process_document()`` function receives a pointer to a callback and a +``void *`` argument. The callback is then invoked with the ``void +*data`` argument that is equal to the provided value. In this typical +case, you can implement it like this (out-of-line API mode):: + + class MyDocument: + ... + + def process(self): + h = ffi.new_handle(self) + lib.process_document(lib.my_callback, # the callback + h, # 'void *data' + args...) + # 'h' stays alive until here, which means that the + # ffi.from_handle() done in my_callback() during + # the call to process_document() is safe + + def callback(self, arg1, arg2): + ... + + # the actual callback is this one-liner global function: + @ffi.def_extern() + def my_callback(arg1, arg2, data): + return ffi.from_handle(data).callback(arg1, arg2) + + +.. _ffi-dlopen: +.. _ffi-dlclose: + +ffi.dlopen(), ffi.dlclose() ++++++++++++++++++++++++++++ + +**ffi.dlopen(libpath, [flags])**: opens and returns a "handle" to a +dynamic library, as a ```` object. See `Preparing and +Distributing modules`_. + +**ffi.dlclose(lib)**: explicitly closes a ```` object returned +by ``ffi.dlopen()``. + +**ffi.RLTD_...**: constants: flags for ``ffi.dlopen()``. + + +ffi.new_allocator() ++++++++++++++++++++ + +**ffi.new_allocator(alloc=None, free=None, should_clear_after_alloc=True)**: +returns a new allocator. An "allocator" is a callable that behaves like +``ffi.new()`` but uses the provided low-level ``alloc`` and ``free`` +functions. *New in version 1.2.* + +``alloc()`` is invoked with the size as sole argument. If it returns +NULL, a MemoryError is raised. Later, if ``free`` is not None, it will +be called with the result of ``alloc()`` as argument. Both can be either +Python function or directly C functions. If only ``free`` is None, then no +free function is called. If both ``alloc`` and ``free`` are None, the +default alloc/free combination is used. (In other words, the call +``ffi.new(*args)`` is equivalent to ``ffi.new_allocator()(*args)``.) + +If ``should_clear_after_alloc`` is set to False, then the memory +returned by ``alloc()`` is assumed to be already cleared (or you are +fine with garbage); otherwise CFFI will clear it. Example: for +performance, if you are using ``ffi.new()`` to allocate large chunks of +memory where the initial content can be left uninitialized, you can do:: + + # at module level + new_nonzero = ffi.new_allocator(should_clear_after_alloc=False) + + # then replace `p = ffi.new("char[]", bigsize)` with: + p = new_nonzero("char[]", bigsize) + +**NOTE:** the following is a general warning that applies particularly +(but not only) to PyPy versions 5.6 or older (PyPy > 5.6 attempts to +account for the memory returned by ``ffi.new()`` or a custom allocator; +and CPython uses reference counting). If you do large allocations, then +there is no hard guarantee about when the memory will be freed. You +should avoid both ``new()`` and ``new_allocator()()`` if you want to be +sure that the memory is promptly released, e.g. before you allocate more +of it. + +An alternative is to declare and call the C ``malloc()`` and ``free()`` +functions, or some variant like ``mmap()`` and ``munmap()``. Then you +control exactly when the memory is allocated and freed. For example, +add these two lines to your existing ``ffibuilder.cdef()``:: + + void *malloc(size_t size); + void free(void *ptr); + +and then call these two functions manually:: + + p = lib.malloc(n * ffi.sizeof("int")) + try: + my_array = ffi.cast("int *", p) + ... + finally: + lib.free(p) + +In cffi version 1.12 you can indeed use ``ffi.new_allocator()`` but use the +``with`` statement (see ``ffi.release()``) to force the free function to be +called at a known point. The above is equivalent to this code:: + + my_new = ffi.new_allocator(lib.malloc, lib.free) # at global level + ... + with my_new("int[]", n) as my_array: + ... + + +.. _ffi-release: + +ffi.release() and the context manager ++++++++++++++++++++++++++++++++++++++ + +**ffi.release(cdata)**: release the resources held by a cdata object from +``ffi.new()``, ``ffi.gc()``, ``ffi.from_buffer()`` or +``ffi.new_allocator()()``. The cdata object must not be used afterwards. +The normal Python destructor of the cdata object releases the same resources, +but this allows the releasing to occur at a known time, as opposed as at an +unspecified point in the future. +*New in version 1.12.* + +``ffi.release(cdata)`` is equivalent to ``cdata.__exit__()``, which means that +you can use the ``with`` statement to ensure that the cdata is released at the +end of a block (in version 1.12 and above):: + + with ffi.from_buffer(...) as p: + do something with p + +The effect is more precisely as follows: + +* on an object returned from ``ffi.gc(destructor)``, ``ffi.release()`` will + cause the ``destructor`` to be called immediately. + +* on an object returned from a custom allocator, the custom free function + is called immediately. + +* on CPython, ``ffi.from_buffer(buf)`` locks the buffer, so ``ffi.release()`` + can be used to unlock it at a known time. On PyPy, there is no locking + (so far); the effect of ``ffi.release()`` is limited to removing the link, + allowing the original buffer object to be garbage-collected even if the + cdata object stays alive. + +* on CPython this method has no effect (so far) on objects returned by + ``ffi.new()``, because the memory is allocated inline with the cdata object + and cannot be freed independently. It might be fixed in future releases of + cffi. + +* on PyPy, ``ffi.release()`` frees the ``ffi.new()`` memory immediately. It is + useful because otherwise the memory is kept alive until the next GC occurs. + If you allocate large amounts of memory with ``ffi.new()`` and don't free + them with ``ffi.release()``, PyPy (>= 5.7) runs its GC more often to + compensate, so the total memory allocated should be kept within bounds + anyway; but calling ``ffi.release()`` explicitly should improve performance + by reducing the frequency of GC runs. + +After ``ffi.release(x)``, do not use anything pointed to by ``x`` any longer. +As an exception to this rule, you can call ``ffi.release(x)`` several times +for the exact same cdata object ``x``; the calls after the first one are +ignored. + + +ffi.init_once() ++++++++++++++++ + +**ffi.init_once(function, tag)**: run ``function()`` once. The +``tag`` should be a primitive object, like a string, that identifies +the function: ``function()`` is only called the first time we see the +``tag``. The return value of ``function()`` is remembered and +returned by the current and all future ``init_once()`` with the same +tag. If ``init_once()`` is called from multiple threads in parallel, +all calls block until the execution of ``function()`` is done. If +``function()`` raises an exception, it is propagated and nothing is +cached (i.e. ``function()`` will be called again, in case we catch the +exception and try ``init_once()`` again). *New in version 1.4.* + +Example:: + + from _xyz_cffi import ffi, lib + + def initlib(): + lib.init_my_library() + + def make_new_foo(): + ffi.init_once(initlib, "init") + return lib.make_foo() + +``init_once()`` is optimized to run very quickly if ``function()`` has +already been called. (On PyPy, the cost is zero---the JIT usually +removes everything in the machine code it produces.) + +*Note:* one motivation__ for ``init_once()`` is the CPython notion of +"subinterpreters" in the embedded case. If you are using the +out-of-line API mode, ``function()`` is called only once even in the +presence of multiple subinterpreters, and its return value is shared +among all subinterpreters. The goal is to mimic the way traditional +CPython C extension modules have their init code executed only once in +total even if there are subinterpreters. In the example above, the C +function ``init_my_library()`` is called once in total, not once per +subinterpreter. For this reason, avoid Python-level side-effects in +``function()`` (as they will only be applied in the first +subinterpreter to run); instead, return a value, as in the following +example:: + + def init_get_max(): + return lib.initialize_once_and_get_some_maximum_number() + + def process(i): + if i > ffi.init_once(init_get_max, "max"): + raise IndexError("index too large!") + ... + +.. __: https://bitbucket.org/cffi/cffi/issues/233/ + + +.. _ffi-getctype: +.. _ffi-list-types: + +ffi.getctype(), ffi.list_types() +++++++++++++++++++++++++++++++++ + +**ffi.getctype("C type" or , extra="")**: return the string +representation of the given C type. If non-empty, the "extra" string is +appended (or inserted at the right place in more complicated cases); it +can be the name of a variable to declare, or an extra part of the type +like ``"*"`` or ``"[5]"``. For example +``ffi.getctype(ffi.typeof(x), "*")`` returns the string representation +of the C type "pointer to the same type than x"; and +``ffi.getctype("char[80]", "a") == "char a[80]"``. + +**ffi.list_types()**: Returns the user type names known to this FFI +instance. This returns a tuple containing three lists of names: +``(typedef_names, names_of_structs, names_of_unions)``. *New in +version 1.6.* + + +.. _`Preparing and Distributing modules`: cdef.html#loading-libraries + + +Conversions +----------- + +This section documents all the conversions that are allowed when +*writing into* a C data structure (or passing arguments to a function +call), and *reading from* a C data structure (or getting the result of a +function call). The last column gives the type-specific operations +allowed. + ++---------------+------------------------+------------------+----------------+ +| C type | writing into | reading from |other operations| ++===============+========================+==================+================+ +| integers | an integer or anything | a Python int or | int(), bool() | +| and enums | on which int() works | long, depending | `[6]`, | +| `[5]` | (but not a float!). | on the type | ``<`` | +| | Must be within range. | (ver. 1.10: or a | | +| | | bool) | | ++---------------+------------------------+------------------+----------------+ +| ``char`` | a string of length 1 | a string of | int(), bool(), | +| | or another | length 1 | ``<`` | ++---------------+------------------------+------------------+----------------+ +| ``wchar_t``, | a unicode of length 1 | a unicode of | | +| ``char16_t``, | (or maybe 2 if | length 1 | int(), | +| ``char32_t`` | surrogates) or | (or maybe 2 if | bool(), ``<`` | +| `[8]` | another similar | surrogates) | | ++---------------+------------------------+------------------+----------------+ +| ``float``, | a float or anything on | a Python float | float(), int(),| +| ``double`` | which float() works | | bool(), ``<`` | ++---------------+------------------------+------------------+----------------+ +|``long double``| another with | a , to | float(), int(),| +| | a ``long double``, or | avoid loosing | bool() | +| | anything on which | precision `[3]` | | +| | float() works | | | ++---------------+------------------------+------------------+----------------+ +| ``float`` | a complex number | a Python complex | complex(), | +| ``_Complex``, | or anything on which | number | bool() | +| ``double`` | complex() works | | `[7]` | +| ``_Complex`` | | | | ++---------------+------------------------+------------------+----------------+ +| pointers | another with | a |``[]`` `[4]`, | +| | a compatible type (i.e.| |``+``, ``-``, | +| | same type | |bool() | +| | or ``void*``, or as an | | | +| | array instead) `[1]` | | | ++---------------+------------------------+ | | +| ``void *`` | another with | | | +| | any pointer or array | | | +| | type | | | ++---------------+------------------------+ +----------------+ +| pointers to | same as pointers | | ``[]``, ``+``, | +| structure or | | | ``-``, bool(), | +| union | | | and read/write | +| | | | struct fields | ++---------------+------------------------+ +----------------+ +| function | same as pointers | | bool(), | +| pointers | | | call `[2]` | ++---------------+------------------------+------------------+----------------+ +| arrays | a list or tuple of | a |len(), iter(), | +| | items | |``[]`` `[4]`, | +| | | |``+``, ``-`` | ++---------------+------------------------+ +----------------+ +| ``char[]``, | same as arrays, or a | | len(), iter(), | +| ``un/signed`` | Python byte string | | ``[]``, ``+``, | +| ``char[]``, | | | ``-`` | +| ``_Bool[]`` | | | | ++---------------+------------------------+ +----------------+ +|``wchar_t[]``, | same as arrays, or a | | len(), iter(), | +|``char16_t[]``,| Python unicode string | | ``[]``, | +|``char32_t[]`` | | | ``+``, ``-`` | +| | | | | ++---------------+------------------------+------------------+----------------+ +| structure | a list or tuple or | a | read/write | +| | dict of the field | | fields | +| | values, or a same-type | | | +| | | | | ++---------------+------------------------+ +----------------+ +| union | same as struct, but | | read/write | +| | with at most one field | | fields | ++---------------+------------------------+------------------+----------------+ + +`[1]` ``item *`` is ``item[]`` in function arguments: + + In a function declaration, as per the C standard, a ``item *`` + 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 + (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}]``. + + As an optimization, CFFI assumes that a + function with a ``char *`` argument to which you pass a Python + string will not actually modify the array of characters passed in, + and so passes directly a pointer inside the Python string object. + (On PyPy, this optimization is only available since PyPy 5.4 + with CFFI 1.8.) + +`[2]` C function calls are done with the GIL released. + + Note that we assume that the called functions are *not* using the + Python API from Python.h. For example, we don't check afterwards + if they set a Python exception. You may work around it, but mixing + CFFI with ``Python.h`` is not recommended. (If you do that, on + PyPy and on some platforms like Windows, you may need to explicitly + link to ``libpypy-c.dll`` to access the CPython C API compatibility + layer; indeed, CFFI-generated modules on PyPy don't link to + ``libpypy-c.dll`` on their own. But really, don't do that in the + first place.) + +`[3]` ``long double`` support: + + We keep ``long double`` values inside a cdata object to avoid + loosing precision. Normal Python floating-point numbers only + contain enough precision for a ``double``. If you really want to + convert such an object to a regular Python float (i.e. a C + ``double``), call ``float()``. If you need to do arithmetic on + such numbers without any precision loss, you need instead to define + and use a family of C functions like ``long double add(long double + a, long double b);``. + +`[4]` Slicing with ``x[start:stop]``: + + Slicing is allowed, as long as you specify explicitly both ``start`` + and ``stop`` (and don't give any ``step``). It gives a cdata + object that is a "view" of all items from ``start`` to ``stop``. + It is a cdata of type "array" (so e.g. passing it as an argument to a + C function would just convert it to a pointer to the ``start`` item). + As with indexing, negative bounds mean really negative indices, like in + C. As for slice assignment, it accepts any iterable, including a list + of items or another array-like cdata object, but the length must match. + (Note that this behavior differs from initialization: e.g. you can + say ``chararray[10:15] = "hello"``, but the assigned string must be of + exactly the correct length; no implicit null character is added.) + +`[5]` Enums are handled like ints: + + Like C, enum types are mostly int types (unsigned or signed, int or + long; note that GCC's first choice is unsigned). Reading an enum + field of a structure, for example, returns you an integer. To + compare their value symbolically, use code like ``if x.field == + lib.FOO``. If you really want to get their value as a string, use + ``ffi.string(ffi.cast("the_enum_type", x.field))``. + +`[6]` bool() on a primitive cdata: + + *New in version 1.7.* In previous versions, it only worked on + pointers; for primitives it always returned True. + + *New in version 1.10:* The C type ``_Bool`` or ``bool`` converts to + Python booleans now. You get an exception if a C ``_Bool`` happens + to contain a value different from 0 and 1 (this case triggers + undefined behavior in C; if you really have to interface with a + library relying on this, don't use ``_Bool`` in the CFFI side). + Also, when converting from a byte string to a ``_Bool[]``, only the + bytes ``\x00`` and ``\x01`` are accepted. + +`[7]` libffi does not support complex numbers: + + *New in version 1.11:* CFFI now supports complex numbers directly. + Note however that libffi does not. This means that C functions that + take directly as argument types or return type a complex type cannot + be called by CFFI, unless they are directly using the API mode. + +`[8]` ``wchar_t``, ``char16_t`` and ``char32_t`` + + See `Unicode character types`_ below. + + +.. _file: + +Support for FILE +++++++++++++++++ + +You can declare C functions taking a ``FILE *`` argument and +call them with a Python file object. If needed, you can also do ``c_f += ffi.cast("FILE *", fileobj)`` and then pass around ``c_f``. + +Note, however, that CFFI does this by a best-effort approach. If you +need finer control over buffering, flushing, and timely closing of the +``FILE *``, then you should not use this special support for ``FILE *``. +Instead, you can handle regular ``FILE *`` cdata objects that you +explicitly make using fdopen(), like this: + +.. code-block:: python + + ffi.cdef(''' + FILE *fdopen(int, const char *); // from the C + int fclose(FILE *); + ''') + + myfile.flush() # make sure the file is flushed + newfd = os.dup(myfile.fileno()) # make a copy of the file descriptor + fp = lib.fdopen(newfd, "w") # make a cdata 'FILE *' around newfd + lib.write_stuff_to_file(fp) # invoke the external function + lib.fclose(fp) # when you're done, close fp (and newfd) + +The special support for ``FILE *`` is anyway implemented in a similar manner +on CPython 3.x and on PyPy, because these Python implementations' files are +not natively based on ``FILE *``. Doing it explicity offers more control. + + +.. _unichar: + +Unicode character types ++++++++++++++++++++++++ + +The ``wchar_t`` type has the same signedness as the underlying +platform's. For example, on Linux, it is a signed 32-bit integer. +However, the types ``char16_t`` and ``char32_t`` (*new in version 1.11*) +are always unsigned. + +Note that CFFI assumes that these types are meant to contain UTF-16 or +UTF-32 characters in the native endianness. More precisely: + +* ``char32_t`` is assumed to contain UTF-32, or UCS4, which is just the + unicode codepoint; + +* ``char16_t`` is assumed to contain UTF-16, i.e. UCS2 plus surrogates; + +* ``wchar_t`` is assumed to contain either UTF-32 or UTF-16 based on its + actual platform-defined size of 4 or 2 bytes. + +Whether this assumption is true or not is unspecified by the C language. +In theory, the C library you are interfacing with could use one of these +types with a different meaning. You would then need to handle it +yourself---for example, by using ``uint32_t`` instead of ``char32_t`` in +the ``cdef()``, and building the expected arrays of ``uint32_t`` +manually. + +Python itself can be compiled with ``sys.maxunicode == 65535`` or +``sys.maxunicode == 1114111`` (Python >= 3.3 is always 1114111). This +changes the handling of surrogates (which are pairs of 16-bit +"characters" which actually stand for a single codepoint whose value is +greater than 65535). If your Python is ``sys.maxunicode == 1114111``, +then it can store arbitrary unicode codepoints; surrogates are +automatically inserted when converting from Python unicodes to UTF-16, +and automatically removed when converting back. On the other hand, if +your Python is ``sys.maxunicode == 65535``, then it is the other way +around: surrogates are removed when converting from Python unicodes +to UTF-32, and added when converting back. In other words, surrogate +conversion is done only when there is a size mismatch. + +Note that Python's internal representations is not specified. For +example, on CPython >= 3.3, it will use 1- or 2- or 4-bytes arrays +depending on what the string actually contains. With CFFI, when you +pass a Python byte string to a C function expecting a ``char*``, then +we pass directly a pointer to the existing data without needing a +temporary buffer; however, the same cannot cleanly be done with +*unicode* string arguments and the ``wchar_t*`` / ``char16_t*`` / +``char32_t*`` types, because of the changing internal +representation. As a result, and for consistency, CFFI always allocates +a temporary buffer for unicode strings. + +**Warning:** for now, if you use ``char16_t`` and ``char32_t`` with +``set_source()``, you have to make sure yourself that the types are +declared by the C source you provide to ``set_source()``. They would be +declared if you ``#include`` a library that explicitly uses them, for +example, or when using C++11. Otherwise, you need ``#include +`` on Linux, or more generally something like ``typedef +uint16_t char16_t;``. This is not done automatically by CFFI because +``uchar.h`` is not standard across platforms, and writing a ``typedef`` +like above would crash if the type happens to be already defined. diff --git a/doc/source/using.rst b/doc/source/using.rst new file mode 100644 index 0000000..ff8e5f1 --- /dev/null +++ b/doc/source/using.rst @@ -0,0 +1,1027 @@ +================================ +Using the ffi/lib objects +================================ + +.. contents:: + +Keep this page under your pillow. + + +.. _working: + +Working with pointers, structures and arrays +-------------------------------------------- + +The C code's integers and floating-point values are mapped to Python's +regular ``int``, ``long`` and ``float``. Moreover, the C type ``char`` +corresponds to single-character strings in Python. (If you want it to +map to small integers, use either ``signed char`` or ``unsigned char``.) + +Similarly, the C type ``wchar_t`` corresponds to single-character +unicode strings. Note that in some situations (a narrow Python build +with an underlying 4-bytes wchar_t type), a single wchar_t character +may correspond to a pair of surrogates, which is represented as a +unicode string of length 2. If you need to convert such a 2-chars +unicode string to an integer, ``ord(x)`` does not work; use instead +``int(ffi.cast('wchar_t', x))``. + +*New in version 1.11:* in addition to ``wchar_t``, the C types +``char16_t`` and ``char32_t`` work the same but with a known fixed size. +In previous versions, this could be achieved using ``uint16_t`` and +``int32_t`` but without automatic conversion to Python unicodes. + +Pointers, structures and arrays are more complex: they don't have an +obvious Python equivalent. Thus, they correspond to objects of type +``cdata``, which are printed for example as +````. + +``ffi.new(ctype, [initializer])``: this function builds and returns a +new cdata object of the given ``ctype``. The ctype is usually some +constant string describing the C type. It must be a pointer or array +type. If it is a pointer, e.g. ``"int *"`` or ``struct foo *``, then +it allocates the memory for one ``int`` or ``struct foo``. If it is +an array, e.g. ``int[10]``, then it allocates the memory for ten +``int``. In both cases the returned cdata is of type ``ctype``. + +The memory is initially filled with zeros. An initializer can be given +too, as described later. + +Example:: + + >>> ffi.new("int *") + + >>> ffi.new("int[10]") + + + >>> ffi.new("char *") # allocates only one char---not a C string! + + >>> ffi.new("char[]", "foobar") # this allocates a C string, ending in \0 + + +Unlike C, the returned pointer object has *ownership* on the allocated +memory: when this exact object is garbage-collected, then the memory is +freed. If, at the level of C, you store a pointer to the memory +somewhere else, then make sure you also keep the object alive for as +long as needed. (This also applies if you immediately cast the returned +pointer to a pointer of a different type: only the original object has +ownership, so you must keep it alive. As soon as you forget it, then +the casted pointer will point to garbage! In other words, the ownership +rules are attached to the *wrapper* cdata objects: they are not, and +cannot, be attached to the underlying raw memory.) Example: + +.. code-block:: python + + global_weakkeydict = weakref.WeakKeyDictionary() + + def make_foo(): + s1 = ffi.new("struct foo *") + fld1 = ffi.new("struct bar *") + fld2 = ffi.new("struct bar *") + s1.thefield1 = fld1 + s1.thefield2 = fld2 + # here the 'fld1' and 'fld2' object must not go away, + # otherwise 's1.thefield1/2' will point to garbage! + global_weakkeydict[s1] = (fld1, fld2) + # now 's1' keeps alive 'fld1' and 'fld2'. When 's1' goes + # away, then the weak dictionary entry will be removed. + return s1 + +Usually you don't need a weak dict: for example, to call a function with +a ``char * *`` argument that contains a pointer to a ``char *`` pointer, +it is enough to do this: + +.. code-block:: python + + p = ffi.new("char[]", "hello, world") # p is a 'char *' + q = ffi.new("char **", p) # q is a 'char **' + lib.myfunction(q) + # p is alive at least until here, so that's fine + +However, this is always wrong (usage of freed memory): + +.. code-block:: python + + p = ffi.new("char **", ffi.new("char[]", "hello, world")) + # WRONG! as soon as p is built, the inner ffi.new() gets freed! + +This is wrong too, for the same reason: + +.. code-block:: python + + p = ffi.new("struct my_stuff") + p.foo = ffi.new("char[]", "hello, world") + # WRONG! as soon as p.foo is set, the ffi.new() gets freed! + + +The cdata objects support mostly the same operations as in C: you can +read or write from pointers, arrays and structures. Dereferencing a +pointer is done usually in C with the syntax ``*p``, which is not valid +Python, so instead you have to use the alternative syntax ``p[0]`` +(which is also valid C). Additionally, the ``p.x`` and ``p->x`` +syntaxes in C both become ``p.x`` in Python. + +We have ``ffi.NULL`` to use in the same places as the C ``NULL``. +Like the latter, it is actually defined to be ``ffi.cast("void *", +0)``. For example, reading a NULL pointer returns a ````, which you can check for e.g. by comparing it with +``ffi.NULL``. + +There is no general equivalent to the ``&`` operator in C (because it +would not fit nicely in the model, and it does not seem to be needed +here). There is `ffi.addressof()`__, but only for some cases. You +cannot take the "address" of a number in Python, for example; similarly, +you cannot take the address of a CFFI pointer. If you have this kind +of C code:: + + int x, y; + fetch_size(&x, &y); + + opaque_t *handle; // some opaque pointer + init_stuff(&handle); // initializes the variable 'handle' + more_stuff(handle); // pass the handle around to more functions + +then you need to rewrite it like this, replacing the variables in C +with what is logically pointers to the variables: + +.. code-block:: python + + px = ffi.new("int *") + py = ffi.new("int *") arr = ffi.new("int[2]") + lib.fetch_size(px, py) -OR- lib.fetch_size(arr, arr + 1) + x = px[0] x = arr[0] + y = py[0] y = arr[1] + + p_handle = ffi.new("opaque_t **") + lib.init_stuff(p_handle) # pass the pointer to the 'handle' pointer + handle = p_handle[0] # now we can read 'handle' out of 'p_handle' + lib.more_stuff(handle) + +.. __: ref.html#ffi-addressof + + +Any operation that would in C return a pointer or array or struct type +gives you a fresh cdata object. Unlike the "original" one, these fresh +cdata objects don't have ownership: they are merely references to +existing memory. + +As an exception to the above rule, dereferencing a pointer that owns a +*struct* or *union* object returns a cdata struct or union object +that "co-owns" the same memory. Thus in this case there are two +objects that can keep the same memory alive. This is done for cases where +you really want to have a struct object but don't have any convenient +place to keep alive the original pointer object (returned by +``ffi.new()``). + +Example: + +.. code-block:: python + + # void somefunction(int *); + + x = ffi.new("int *") # allocate one int, and return a pointer to it + x[0] = 42 # fill it + lib.somefunction(x) # call the C function + print x[0] # read the possibly-changed value + +The equivalent of C casts are provided with ``ffi.cast("type", value)``. +They should work in the same cases as they do in C. Additionally, this +is the only way to get cdata objects of integer or floating-point type:: + + >>> x = ffi.cast("int", 42) + >>> x + + >>> int(x) + 42 + +To cast a pointer to an int, cast it to ``intptr_t`` or ``uintptr_t``, +which are defined by C to be large enough integer types (example on 32 +bits):: + + >>> int(ffi.cast("intptr_t", pointer_cdata)) # signed + -1340782304 + >>> int(ffi.cast("uintptr_t", pointer_cdata)) # unsigned + 2954184992L + +The initializer given as the optional second argument to ``ffi.new()`` +can be mostly anything that you would use as an initializer for C code, +with lists or tuples instead of using the C syntax ``{ .., .., .. }``. +Example:: + + typedef struct { int x, y; } foo_t; + + foo_t v = { 1, 2 }; // C syntax + v = ffi.new("foo_t *", [1, 2]) # CFFI equivalent + + foo_t v = { .y=1, .x=2 }; // C99 syntax + v = ffi.new("foo_t *", {'y': 1, 'x': 2}) # CFFI equivalent + +Like C, arrays of chars can also be initialized from a string, in +which case a terminating null character is appended implicitly:: + + >>> x = ffi.new("char[]", "hello") + >>> x + + >>> len(x) # the actual size of the array + 6 + >>> x[5] # the last item in the array + '\x00' + >>> x[0] = 'H' # change the first item + >>> ffi.string(x) # interpret 'x' as a regular null-terminated string + 'Hello' + +Similarly, arrays of wchar_t or char16_t or char32_t can be initialized +from a unicode string, +and calling ``ffi.string()`` on the cdata object returns the current unicode +string stored in the source array (adding surrogates if necessary). +See the `Unicode character types`__ section for more details. + +.. __: ref.html#unichar + +Note that unlike Python lists or tuples, but like C, you *cannot* index in +a C array from the end using negative numbers. + +More generally, the C array types can have their length unspecified in C +types, as long as their length can be derived from the initializer, like +in C:: + + int array[] = { 1, 2, 3, 4 }; // C syntax + array = ffi.new("int[]", [1, 2, 3, 4]) # CFFI equivalent + +As an extension, the initializer can also be just a number, giving +the length (in case you just want zero-initialization):: + + int array[1000]; // C syntax + array = ffi.new("int[1000]") # CFFI 1st equivalent + array = ffi.new("int[]", 1000) # CFFI 2nd equivalent + +This is useful if the length is not actually a constant, to avoid things +like ``ffi.new("int[%d]" % x)``. Indeed, this is not recommended: +``ffi`` normally caches the string ``"int[]"`` to not need to re-parse +it all the time. + +The C99 variable-sized structures are supported too, as long as the +initializer says how long the array should be: + +.. code-block:: python + + # typedef struct { int x; int y[]; } foo_t; + + p = ffi.new("foo_t *", [5, [6, 7, 8]]) # length 3 + p = ffi.new("foo_t *", [5, 3]) # length 3 with 0 in the array + p = ffi.new("foo_t *", {'y': 3}) # length 3 with 0 everywhere + +Finally, note that any Python object used as initializer can also be +used directly without ``ffi.new()`` in assignments to array items or +struct fields. In fact, ``p = ffi.new("T*", initializer)`` is +equivalent to ``p = ffi.new("T*"); p[0] = initializer``. Examples: + +.. code-block:: python + + # if 'p' is a + p[2] = [10, 20] # writes to p[2][0] and p[2][1] + + # if 'p' is a , and foo_t has fields x, y and z + p[0] = {'x': 10, 'z': 20} # writes to p.x and p.z; p.y unmodified + + # if, on the other hand, foo_t has a field 'char a[5]': + p.a = "abc" # writes 'a', 'b', 'c' and '\0'; p.a[4] unmodified + +In function calls, when passing arguments, these rules can be used too; +see `Function calls`_. + + +Python 3 support +---------------- + +Python 3 is supported, but the main point to note is that the ``char`` C +type corresponds to the ``bytes`` Python type, and not ``str``. It is +your responsibility to encode/decode all Python strings to bytes when +passing them to or receiving them from CFFI. + +This only concerns the ``char`` type and derivative types; other parts +of the API that accept strings in Python 2 continue to accept strings in +Python 3. + + +An example of calling a main-like thing +--------------------------------------- + +Imagine we have something like this: + +.. code-block:: python + + from cffi import FFI + ffi = FFI() + ffi.cdef(""" + int main_like(int argv, char *argv[]); + """) + lib = ffi.dlopen("some_library.so") + +Now, everything is simple, except, how do we create the ``char**`` argument +here? +The first idea: + +.. code-block:: python + + lib.main_like(2, ["arg0", "arg1"]) + +does not work, because the initializer receives two Python ``str`` objects +where it was expecting ```` objects. You need to use +``ffi.new()`` explicitly to make these objects: + +.. code-block:: python + + lib.main_like(2, [ffi.new("char[]", "arg0"), + ffi.new("char[]", "arg1")]) + +Note that the two ```` objects are kept alive for the +duration of the call: they are only freed when the list itself is freed, +and the list is only freed when the call returns. + +If you want instead to build an "argv" variable that you want to reuse, +then more care is needed: + +.. code-block:: python + + # DOES NOT WORK! + argv = ffi.new("char *[]", [ffi.new("char[]", "arg0"), + ffi.new("char[]", "arg1")]) + +In the above example, the inner "arg0" string is deallocated as soon +as "argv" is built. You have to make sure that you keep a reference +to the inner "char[]" objects, either directly or by keeping the list +alive like this: + +.. code-block:: python + + argv_keepalive = [ffi.new("char[]", "arg0"), + ffi.new("char[]", "arg1")] + argv = ffi.new("char *[]", argv_keepalive) + + +Function calls +-------------- + +When calling C functions, passing arguments follows mostly the same +rules as assigning to structure fields, and the return value follows the +same rules as reading a structure field. For example: + +.. code-block:: python + + # int foo(short a, int b); + + n = lib.foo(2, 3) # returns a normal integer + lib.foo(40000, 3) # raises OverflowError + +You can pass to ``char *`` arguments a normal Python string (but don't +pass a normal Python string to functions that take a ``char *`` +argument and may mutate it!): + +.. code-block:: python + + # size_t strlen(const char *); + + assert lib.strlen("hello") == 5 + +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 +use ``type *`` or ``type[]``. For example, ``int *`` is fully +equivalent to ``int[]`` (or even ``int[5]``; the 5 is ignored). For CFFI, +this means that you can always pass arguments that can be converted to +either ``int *`` or ``int[]``. For example: + +.. code-block:: python + + # void do_something_with_array(int *array); + + lib.do_something_with_array([1, 2, 3, 4, 5]) # works for int[] + +See `Reference: conversions`__ for a similar way to pass ``struct foo_s +*`` arguments---but in general, it is clearer in this case to pass +``ffi.new('struct foo_s *', initializer)``. + +__ ref.html#conversions + +CFFI supports passing and returning structs and unions to functions and +callbacks. Example: + +.. code-block:: python + + # struct foo_s { int a, b; }; + # struct foo_s function_returning_a_struct(void); + + myfoo = lib.function_returning_a_struct() + # `myfoo`: + +For performance, non-variadic API-level functions that you get by +writing ``lib.some_function`` are not ```` +objects, but an object of a different type (on CPython, ````). This means you cannot pass them directly to some other C +function expecting a function pointer argument. Only ``ffi.typeof()`` +works on them. To get a cdata containing a regular function pointer, +use ``ffi.addressof(lib, "name")``. + +There are a few (obscure) limitations to the supported argument and +return types. These limitations come from libffi and apply only to +calling ```` function pointers; in other words, they don't +apply to non-variadic ``cdef()``-declared functions if you are using +the API mode. The limitations are that you cannot pass directly as +argument or return type: + +* a union (but a *pointer* to a union is fine); + +* a struct which uses bitfields (but a *pointer* to such a struct is + fine); + +* a struct that was declared with "``...``" in the ``cdef()``. + +In API mode, you can work around these limitations: for example, if you +need to call such a function pointer from Python, you can instead write +a custom C function that accepts the function pointer and the real +arguments and that does the call from C. Then declare that custom C +function in the ``cdef()`` and use it from Python. + + +Variadic function calls +----------------------- + +Variadic functions in C (which end with "``...``" as their last +argument) can be declared and called normally, with the exception that +all the arguments passed in the variable part *must* be cdata objects. +This is because it would not be possible to guess, if you wrote this:: + + lib.printf("hello, %d\n", 42) # doesn't work! + +that you really meant the 42 to be passed as a C ``int``, and not a +``long`` or ``long long``. The same issue occurs with ``float`` versus +``double``. So you have to force cdata objects of the C type you want, +if necessary with ``ffi.cast()``: + +.. code-block:: python + + lib.printf("hello, %d\n", ffi.cast("int", 42)) + lib.printf("hello, %ld\n", ffi.cast("long", 42)) + lib.printf("hello, %f\n", ffi.cast("double", 42)) + +But of course: + +.. code-block:: python + + lib.printf("hello, %s\n", ffi.new("char[]", "world")) + +Note that if you are using ``dlopen()``, the function declaration in the +``cdef()`` must match the original one in C exactly, as usual --- in +particular, if this function is variadic in C, then its ``cdef()`` +declaration must also be variadic. You cannot declare it in the +``cdef()`` with fixed arguments instead, even if you plan to only call +it with these argument types. The reason is that some architectures +have a different calling convention depending on whether the function +signature is fixed or not. (On x86-64, the difference can sometimes be +seen in PyPy's JIT-generated code if some arguments are ``double``.) + +Note that the function signature ``int foo();`` is interpreted by CFFI +as equivalent to ``int foo(void);``. This differs from the C standard, +in which ``int foo();`` is really like ``int foo(...);`` and can be +called with any arguments. (This feature of C is a pre-C89 relic: the +arguments cannot be accessed at all in the body of ``foo()`` without +relying on compiler-specific extensions. Nowadays virtually all code +with ``int foo();`` really means ``int foo(void);``.) + + +Memory pressure (PyPy) +---------------------- + +This paragraph applies only to PyPy, because its garbage collector (GC) +is different from CPython's. It is very common in C code to have pairs +of functions, one which performs memory allocations or acquires other +resources, and the other which frees them again. Depending on how you +structure your Python code, the freeing function is only called when the +GC decides a particular (Python) object can be freed. This occurs +notably in these cases: + +* If you use a ``__del__()`` method to call the freeing function. + +* If you use ``ffi.gc()`` without also using ``ffi.release()``. + +* This does not occur if you call the freeing function at a + deterministic time, like in a regular ``try: finally:`` block. It + does however occur *inside a generator---* if the generator is not + explicitly exhausted but forgotten at a ``yield`` point, then the code + in the enclosing ``finally`` block is only invoked at the next GC. + +In these cases, you may have to use the built-in function +``__pypy__.add_memory_pressure(n)``. Its argument ``n`` is an estimate +of how much memory pressure to add. For example, if the pair of C +functions that we are talking about is ``malloc(n)`` and ``free()`` or +similar, you would call ``__pypy__.add_memory_pressure(n)`` after +``malloc(n)``. Doing so is not always a complete answer to the problem, +but it makes the next GC occur earlier, which is often enough. + +The same applies if the memory allocations are indirect, e.g. the C +function allocates some internal data structures. In that case, call +``__pypy__.add_memory_pressure(n)`` with an argument ``n`` that is an +rough estimation. Knowing the exact size is not important, and memory +pressure doesn't have to be manually brought down again after calling +the freeing function. If you are writing wrappers for the allocating / +freeing pair of functions, you should probably call +``__pypy__.add_memory_pressure()`` in the former even if the user may +invoke the latter at a known point with a ``finally:`` block. + +In case this solution is not sufficient, or if the acquired resource is +not memory but something else more limited (like file descriptors), then +there is no better way than restructuring your code to make sure the +freeing function is called at a known point and not indirectly by the +GC. + +Note that in PyPy <= 5.6 the discussion above also applies to +``ffi.new()``. In more recent versions of PyPy, both ``ffi.new()`` and +``ffi.new_allocator()()`` automatically account for the memory pressure +they create. (In case you need to support both older and newer PyPy's, +try calling ``__pypy__.add_memory_pressure()`` anyway; it is better to +overestimate than not account for the memory pressure.) + + +.. _extern-python: +.. _`extern "Python"`: + +Extern "Python" (new-style callbacks) +------------------------------------- + +When the C code needs a pointer to a function which invokes back a +Python function of your choice, here is how you do it in the +out-of-line API mode. The next section about Callbacks_ describes the +ABI-mode solution. + +This is *new in version 1.4.* Use old-style Callbacks_ if backward +compatibility is an issue. (The original callbacks are slower to +invoke and have the same issue as libffi's callbacks; notably, see the +warning__. The new style described in the present section does not +use libffi's callbacks at all.) + +.. __: Callbacks_ + +In the builder script, declare in the cdef a function prefixed with +``extern "Python"``:: + + ffibuilder.cdef(""" + extern "Python" int my_callback(int, int); + + void library_function(int(*callback)(int, int)); + """) + ffibuilder.set_source("_my_example", r""" + #include + """) + +The function ``my_callback()`` is then implemented in Python inside +your application's code:: + + from _my_example import ffi, lib + + @ffi.def_extern() + def my_callback(x, y): + return 42 + +You obtain a ```` pointer-to-function object by getting +``lib.my_callback``. This ```` can be passed to C code and +then works like a callback: when the C code calls this function +pointer, the Python function ``my_callback`` is called. (You need +to pass ``lib.my_callback`` to C code, and not ``my_callback``: the +latter is just the Python function above, which cannot be passed to C.) + +CFFI implements this by defining ``my_callback`` as a static C +function, written after the ``set_source()`` code. The ```` +then points to this function. What this function does is invoke the +Python function object that is, at runtime, attached with +``@ffi.def_extern()``. + +The ``@ffi.def_extern()`` decorator should be applied to **global +functions,** one for each ``extern "Python"`` function of the same +name. + +To support some corner cases, it is possible to redefine the attached +Python function by calling ``@ffi.def_extern()`` again for the same +name---but this is not recommended! Better attach a single global +Python function for this name, and write it more flexibly in the first +place. This is because each ``extern "Python"`` function turns into +only one C function. Calling ``@ffi.def_extern()`` again changes this +function's C logic to call the new Python function; the old Python +function is not callable any more. The C function pointer you get +from ``lib.my_function`` is always this C function's address, i.e. it +remains the same. + +Extern "Python" and ``void *`` arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As described just before, you cannot use ``extern "Python"`` to make a +variable number of C function pointers. However, achieving that +result is not possible in pure C code either. For this reason, it is +usual for C to define callbacks with a ``void *data`` argument. You +can use ``ffi.new_handle()`` and ``ffi.from_handle()`` to pass a +Python object through this ``void *`` argument. For example, if the C +type of the callbacks is:: + + typedef void (*event_cb_t)(event_t *evt, void *userdata); + +and you register events by calling this function:: + + void event_cb_register(event_cb_t cb, void *userdata); + +Then you would write this in the build script:: + + ffibuilder.cdef(""" + typedef ... event_t; + typedef void (*event_cb_t)(event_t *evt, void *userdata); + void event_cb_register(event_cb_t cb, void *userdata); + + extern "Python" void my_event_callback(event_t *, void *); + """) + ffibuilder.set_source("_demo_cffi", r""" + #include + """) + +and in your main application you register events like this:: + + from _demo_cffi import ffi, lib + + class Widget(object): + def __init__(self): + userdata = ffi.new_handle(self) + self._userdata = userdata # must keep this alive! + lib.event_cb_register(lib.my_event_callback, userdata) + + def process_event(self, evt): + print "got event!" + + @ffi.def_extern() + def my_event_callback(evt, userdata): + widget = ffi.from_handle(userdata) + widget.process_event(evt) + +Some other libraries don't have an explicit ``void *`` argument, but +let you attach the ``void *`` to an existing structure. For example, +the library might say that ``widget->userdata`` is a generic field +reserved for the application. If the event's signature is now this:: + + typedef void (*event_cb_t)(widget_t *w, event_t *evt); + +Then you can use the ``void *`` field in the low-level +``widget_t *`` like this:: + + from _demo_cffi import ffi, lib + + class Widget(object): + def __init__(self): + ll_widget = lib.new_widget(500, 500) + self.ll_widget = ll_widget # + userdata = ffi.new_handle(self) + self._userdata = userdata # must still keep this alive! + ll_widget.userdata = userdata # this makes a copy of the "void *" + lib.event_cb_register(ll_widget, lib.my_event_callback) + + def process_event(self, evt): + print "got event!" + + @ffi.def_extern() + def my_event_callback(ll_widget, evt): + widget = ffi.from_handle(ll_widget.userdata) + widget.process_event(evt) + +Extern "Python" accessed from C directly +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In case you want to access some ``extern "Python"`` function directly +from the C code written in ``set_source()``, you need to write a +forward declaration. (By default it needs to be static, but see +`next paragraph`__.) The real implementation of this function +is added by CFFI *after* the C code---this is needed because the +declaration might use types defined by ``set_source()`` +(e.g. ``event_t`` above, from the ``#include``), so it cannot be +generated before. + +.. __: `extern-python-c`_ + +:: + + ffibuilder.set_source("_demo_cffi", r""" + #include + + static void my_event_callback(widget_t *, event_t *); + + /* here you can write C code which uses '&my_event_callback' */ + """) + +This can also be used to write custom C code which calls Python +directly. Here is an example (inefficient in this case, but might be +useful if the logic in ``my_algo()`` is much more complex):: + + ffibuilder.cdef(""" + extern "Python" int f(int); + int my_algo(int); + """) + ffibuilder.set_source("_example_cffi", r""" + static int f(int); /* the forward declaration */ + + static int my_algo(int n) { + int i, sum = 0; + for (i = 0; i < n; i++) + sum += f(i); /* call f() here */ + return sum; + } + """) + +.. _extern-python-c: + +Extern "Python+C" +~~~~~~~~~~~~~~~~~ + +Functions declared with ``extern "Python"`` are generated as +``static`` functions in the C source. However, in some cases it is +convenient to make them non-static, typically when you want to make +them directly callable from other C source files. To do that, you can +say ``extern "Python+C"`` instead of just ``extern "Python"``. *New +in version 1.6.* + ++------------------------------------+--------------------------------------+ +| if the cdef contains | then CFFI generates | ++------------------------------------+--------------------------------------+ +| ``extern "Python" int f(int);`` | ``static int f(int) { /* code */ }`` | ++------------------------------------+--------------------------------------+ +| ``extern "Python+C" int f(int);`` | ``int f(int) { /* code */ }`` | ++------------------------------------+--------------------------------------+ + +The name ``extern "Python+C"`` comes from the fact that we want an +extern function in both senses: as an ``extern "Python"``, and as a +C function that is not static. + +You cannot make CFFI generate additional macros or other +compiler-specific stuff like the GCC ``__attribute__``. You can only +control whether the function should be ``static`` or not. But often, +these attributes must be written alongside the function *header*, and +it is fine if the function *implementation* does not repeat them:: + + ffibuilder.cdef(""" + extern "Python+C" int f(int); /* not static */ + """) + ffibuilder.set_source("_example_cffi", r""" + /* the forward declaration, setting a gcc attribute + (this line could also be in some .h file, to be included + both here and in the other C files of the project) */ + int f(int) __attribute__((visibility("hidden"))); + """) + + +Extern "Python": reference +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``extern "Python"`` must appear in the cdef(). Like the C++ ``extern +"C"`` syntax, it can also be used with braces around a group of +functions:: + + extern "Python" { + int foo(int); + int bar(int); + } + +The ``extern "Python"`` functions cannot be variadic for now. This +may be implemented in the future. (`This demo`__ shows how to do it +anyway, but it is a bit lengthy.) + +.. __: https://bitbucket.org/cffi/cffi/src/default/demo/extern_python_varargs.py + +Each corresponding Python callback function is defined with the +``@ffi.def_extern()`` decorator. Be careful when writing this +function: if it raises an exception, or tries to return an object of +the wrong type, then the exception cannot be propagated. Instead, the +exception is printed to stderr and the C-level callback is made to +return a default value. This can be controlled with ``error`` and +``onerror``, described below. + +.. _def-extern: + +The ``@ffi.def_extern()`` decorator takes these optional arguments: + +* ``name``: the name of the function as written in the cdef. By default + it is taken from the name of the Python function you decorate. + +.. _error_onerror: + +* ``error``: the returned value in case the Python function raises an + exception. It is 0 or null by default. The exception is still + printed to stderr, so this should be used only as a last-resort + solution. + +* ``onerror``: if you want to be sure to catch all exceptions, use + ``@ffi.def_extern(onerror=my_handler)``. If an exception occurs and + ``onerror`` is specified, then ``onerror(exception, exc_value, + traceback)`` is called. This is useful in some situations where you + cannot simply write ``try: except:`` in the main callback function, + because it might not catch exceptions raised by signal handlers: if + a signal occurs while in C, the Python signal handler is called as + soon as possible, which is after entering the callback function but + *before* executing even the ``try:``. If the signal handler raises, + we are not in the ``try: except:`` yet. + + If ``onerror`` is called and returns normally, then it is assumed + that it handled the exception on its own and nothing is printed to + stderr. If ``onerror`` raises, then both tracebacks are printed. + Finally, ``onerror`` can itself provide the result value of the + callback in C, but doesn't have to: if it simply returns None---or + if ``onerror`` itself fails---then the value of ``error`` will be + used, if any. + + Note the following hack: in ``onerror``, you can access the original + callback arguments as follows. First check if ``traceback`` is not + None (it is None e.g. if the whole function ran successfully but + there was an error converting the value returned: this occurs after + the call). If ``traceback`` is not None, then + ``traceback.tb_frame`` is the frame of the outermost function, + i.e. directly the frame of the function decorated with + ``@ffi.def_extern()``. So you can get the value of ``argname`` in + that frame by reading ``traceback.tb_frame.f_locals['argname']``. + + +.. _Callbacks: + +Callbacks (old style) +--------------------- + +Here is how to make a new ```` object that contains a pointer +to a function, where that function invokes back a Python function of +your choice:: + + >>> @ffi.callback("int(int, int)") + >>> def myfunc(x, y): + ... return x + y + ... + >>> myfunc + > + +Note that ``"int(*)(int, int)"`` is a C *function pointer* type, whereas +``"int(int, int)"`` is a C *function* type. Either can be specified to +ffi.callback() and the result is the same. + +.. warning:: + + Callbacks are provided for the ABI mode or for backward + compatibility. If you are using the out-of-line API mode, it is + recommended to use the `extern "Python"`_ mechanism instead of + callbacks: it gives faster and cleaner code. It also avoids several + issues with old-style callbacks: + + - On less common architecture, libffi is more likely to crash on + callbacks (`e.g. on NetBSD`__); + + - On hardened systems like PAX and SELinux, the extra memory + protections can interfere (for example, on SELinux you need to + run with ``deny_execmem`` set to ``off``). + + Note also that a cffi fix for the latter issue was attempted---see + the ``ffi_closure_alloc`` branch---but was not merged because it + creates potential `memory corruption`__ with ``fork()``. + +.. __: https://github.com/pyca/pyopenssl/issues/596 +.. __: https://bugzilla.redhat.com/show_bug.cgi?id=1249685 + +Warning: like ffi.new(), ffi.callback() returns a cdata that has +ownership of its C data. (In this case, the necessary C data contains +the libffi data structures to do a callback.) This means that the +callback can only be invoked as long as this cdata object is alive. +If you store the function pointer into C code, then make sure you also +keep this object alive for as long as the callback may be invoked. +The easiest way to do that is to always use ``@ffi.callback()`` at +module-level only, and to pass "context" information around with +`ffi.new_handle()`__, if possible. Example: + +.. __: ref.html#new-handle + +.. code-block:: python + + # a good way to use this decorator is once at global level + @ffi.callback("int(int, void *)") + def my_global_callback(x, handle): + return ffi.from_handle(handle).some_method(x) + + + class Foo(object): + + def __init__(self): + handle = ffi.new_handle(self) + self._handle = handle # must be kept alive + lib.register_stuff_with_callback_and_voidp_arg(my_global_callback, handle) + + def some_method(self, x): + print "method called!" + +(See also the section about `extern "Python"`_ above, where the same +general style is used.) + +Note that callbacks of a variadic function type are not supported. A +workaround is to add custom C code. In the following example, a +callback gets a first argument that counts how many extra ``int`` +arguments are passed: + +.. code-block:: python + + # file "example_build.py" + + import cffi + + ffibuilder = cffi.FFI() + ffibuilder.cdef(""" + int (*python_callback)(int how_many, int *values); + void *const c_callback; /* pass this const ptr to C routines */ + """) + ffibuilder.set_source("_example", r""" + #include + #include + static int (*python_callback)(int how_many, int *values); + static int c_callback(int how_many, ...) { + va_list ap; + /* collect the "..." arguments into the values[] array */ + int i, *values = alloca(how_many * sizeof(int)); + va_start(ap, how_many); + for (i=0; i`` compares equal to ``42`` and ```` + compares equal to ``b'A'``. Unlike C, ```` does not + compare equal to ``ffi.cast("unsigned int", -1)``: it compares + smaller, because ``-1 < 4294967295``. + +* PyPy: ``ffi.new()`` and ``ffi.new_allocator()()`` did not record + "memory pressure", causing the GC to run too infrequently if you call + ``ffi.new()`` very often and/or with large arrays. Fixed in PyPy 5.7. + +* Support in ``ffi.cdef()`` for numeric expressions with ``+`` or + ``-``. Assumes that there is no overflow; it should be fixed first + before we add more general support for arbitrary arithmetic on + constants. + + +v1.9 +---- + +* Structs with variable-sized arrays as their last field: now we track + the length of the array after ``ffi.new()`` is called, just like we + always tracked the length of ``ffi.new("int[]", 42)``. This lets us + detect out-of-range accesses to array items. This also lets us + display a better ``repr()``, and have the total size returned by + ``ffi.sizeof()`` and ``ffi.buffer()``. Previously both functions + would return a result based on the size of the declared structure + type, with an assumed empty array. (Thanks andrew for starting this + refactoring.) + +* Add support in ``cdef()/set_source()`` for unspecified-length arrays + in typedefs: ``typedef int foo_t[...];``. It was already supported + for global variables or structure fields. + +* I turned in v1.8 a warning from ``cffi/model.py`` into an error: + ``'enum xxx' has no values explicitly defined: refusing to guess which + integer type it is meant to be (unsigned/signed, int/long)``. Now I'm + turning it back to a warning again; it seems that guessing that the + enum has size ``int`` is a 99%-safe bet. (But not 100%, so it stays + as a warning.) + +* Fix leaks in the code handling ``FILE *`` arguments. In CPython 3 + there is a remaining issue that is hard to fix: if you pass a Python + file object to a ``FILE *`` argument, then ``os.dup()`` is used and + the new file descriptor is only closed when the GC reclaims the Python + file object---and not at the earlier time when you call ``close()``, + which only closes the original file descriptor. If this is an issue, + you should avoid this automatic convertion of Python file objects: + instead, explicitly manipulate file descriptors and call ``fdopen()`` + from C (...via cffi). + + +v1.8.3 +------ + +* When passing a ``void *`` argument to a function with a different + pointer type, or vice-versa, the cast occurs automatically, like in C. + The same occurs for initialization with ``ffi.new()`` and a few other + places. However, I thought that ``char *`` had the same + property---but I was mistaken. In C you get the usual warning if you + try to give a ``char *`` to a ``char **`` argument, for example. + Sorry about the confusion. This has been fixed in CFFI by giving for + now a warning, too. It will turn into an error in a future version. + + +v1.8.2 +------ + +* Issue #283: fixed ``ffi.new()`` on structures/unions with nested + anonymous structures/unions, when there is at least one union in + the mix. When initialized with a list or a dict, it should now + behave more closely like the ``{ }`` syntax does in GCC. + + +v1.8.1 +------ + +* CPython 3.x: experimental: the generated C extension modules now use + the "limited API", which means that, as a compiled .so/.dll, it should + work directly on any version of CPython >= 3.2. The name produced by + distutils is still version-specific. To get the version-independent + name, you can rename it manually to ``NAME.abi3.so``, or use the very + recent setuptools 26. + +* Added ``ffi.compile(debug=...)``, similar to ``python setup.py build + --debug`` but defaulting to True if we are running a debugging + version of Python itself. + + +v1.8 +---- + +* Removed the restriction that ``ffi.from_buffer()`` cannot be used on + byte strings. Now you can get a ``char *`` out of a byte string, + which is valid as long as the string object is kept alive. (But + don't use it to *modify* the string object! If you need this, use + ``bytearray`` or other official techniques.) + +* PyPy 5.4 can now pass a byte string directly to a ``char *`` + argument (in older versions, a copy would be made). This used to be + a CPython-only optimization. + + +v1.7 +---- + +* ``ffi.gc(p, None)`` removes the destructor on an object previously + created by another call to ``ffi.gc()`` + +* ``bool(ffi.cast("primitive type", x))`` now returns False if the + value is zero (including ``-0.0``), and True otherwise. Previously + this would only return False for cdata objects of a pointer type when + the pointer is NULL. + +* bytearrays: ``ffi.from_buffer(bytearray-object)`` is now supported. + (The reason it was not supported was that it was hard to do in PyPy, + but it works since PyPy 5.3.) To call a C function with a ``char *`` + argument from a buffer object---now including bytearrays---you write + ``lib.foo(ffi.from_buffer(x))``. Additionally, this is now supported: + ``p[0:length] = bytearray-object``. The problem with this was that a + iterating over bytearrays gives *numbers* instead of *characters*. + (Now it is implemented with just a memcpy, of course, not actually + iterating over the characters.) + +* C++: compiling the generated C code with C++ was supposed to work, + but failed if you make use the ``bool`` type (because that is rendered + as the C ``_Bool`` type, which doesn't exist in C++). + +* ``help(lib)`` and ``help(lib.myfunc)`` now give useful information, + as well as ``dir(p)`` where ``p`` is a struct or pointer-to-struct. + + +v1.6 +---- + +* `ffi.list_types()`_ + +* `ffi.unpack()`_ + +* `extern "Python+C"`_ + +* in API mode, ``lib.foo.__doc__`` contains the C signature now. On + CPython you can say ``help(lib.foo)``, but for some reason + ``help(lib)`` (or ``help(lib.foo)`` on PyPy) is still useless; I + haven't yet figured out the hacks needed to convince ``pydoc`` to + show more. (You can use ``dir(lib)`` but it is not most helpful.) + +* Yet another attempt at robustness of ``ffi.def_extern()`` against + CPython's interpreter shutdown logic. + +.. _`ffi.list_types()`: ref.html#ffi-list-types +.. _`ffi.unpack()`: ref.html#ffi-unpack +.. _`extern "Python+C"`: using.html#extern-python-c + + +v1.5.2 +------ + +* Fix 1.5.1 for Python 2.6. + + +v1.5.1 +------ + +* A few installation-time tweaks (thanks Stefano!) + +* Issue #245: Win32: ``__stdcall`` was never generated for + ``extern "Python"`` functions + +* Issue #246: trying to be more robust against CPython's fragile + interpreter shutdown logic + + +v1.5.0 +------ + +* Support for `using CFFI for embedding`__. + +.. __: embedding.html + + +v1.4.2 +------ + +Nothing changed from v1.4.1. + + +v1.4.1 +------ + +* Fix the compilation failure of cffi on CPython 3.5.0. (3.5.1 works; + some detail changed that makes some underscore-starting macros + disappear from view of extension modules, and I worked around it, + thinking it changed in all 3.5 versions---but no: it was only in + 3.5.1.) + + +v1.4.0 +------ + +* A `better way to do callbacks`__ has been added (faster and more + portable, and usually cleaner). It is a mechanism for the + out-of-line API mode that replaces the dynamic creation of callback + objects (i.e. C functions that invoke Python) with the static + declaration in ``cdef()`` of which callbacks are needed. This is + more C-like, in that you have to structure your code around the idea + that you get a fixed number of function pointers, instead of + creating them on-the-fly. + +* ``ffi.compile()`` now takes an optional ``verbose`` argument. When + ``True``, distutils prints the calls to the compiler. + +* ``ffi.compile()`` used to fail if given ``sources`` with a path that + includes ``".."``. Fixed. + +* ``ffi.init_once()`` added. See docs__. + +* ``dir(lib)`` now works on libs returned by ``ffi.dlopen()`` too. + +* Cleaned up and modernized the content of the ``demo`` subdirectory + in the sources (thanks matti!). + +* ``ffi.new_handle()`` is now guaranteed to return unique ``void *`` + values, even if called twice on the same object. Previously, in + that case, CPython would return two ``cdata`` objects with the same + ``void *`` value. This change is useful to add and remove handles + from a global dict (or set) without worrying about duplicates. + It already used to work like that on PyPy. + *This change can break code that used to work on CPython by relying + on the object to be kept alive by other means than keeping the + result of ffi.new_handle() alive.* (The corresponding `warning in + the docs`__ of ``ffi.new_handle()`` has been here since v0.8!) + +.. __: using.html#extern-python +.. __: ref.html#ffi-init-once +.. __: ref.html#ffi-new-handle + + +v1.3.1 +------ + +* The optional typedefs (``bool``, ``FILE`` and all Windows types) were + not always available from out-of-line FFI objects. + +* Opaque enums are phased out from the cdefs: they now give a warning, + instead of (possibly wrongly) being assumed equal to ``unsigned int``. + Please report if you get a reasonable use case for them. + +* Some parsing details, notably ``volatile`` is passed along like + ``const`` and ``restrict``. Also, older versions of pycparser + mis-parse some pointer-to-pointer types like ``char * const *``: the + "const" ends up at the wrong place. Added a workaround. + + +v1.3.0 +------ + +* Added `ffi.memmove()`_. + +* Pull request #64: out-of-line API mode: we can now declare + floating-point types with ``typedef float... foo_t;``. This only + works if ``foo_t`` is a float or a double, not ``long double``. + +* Issue #217: fix possible unaligned pointer manipulation, which crashes + on some architectures (64-bit, non-x86). + +* Issues #64 and #126: when using ``set_source()`` or ``verify()``, + the ``const`` and ``restrict`` keywords are copied from the cdef + to the generated C code; this fixes warnings by the C compiler. + It also fixes corner cases like ``typedef const int T; T a;`` + which would previously not consider ``a`` as a constant. (The + cdata objects themselves are never ``const``.) + +* Win32: support for ``__stdcall``. For callbacks and function + pointers; regular C functions still don't need to have their `calling + convention`_ declared. + +* Windows: CPython 2.7 distutils doesn't work with Microsoft's official + Visual Studio for Python, and I'm told this is `not a bug`__. For + ffi.compile(), we `removed a workaround`__ that was inside cffi but + which had unwanted side-effects. Try saying ``import setuptools`` + first, which patches distutils... + +.. _`ffi.memmove()`: ref.html#ffi-memmove +.. __: https://bugs.python.org/issue23246 +.. __: https://bitbucket.org/cffi/cffi/pull-requests/65/remove-_hack_at_distutils-which-imports/diff +.. _`calling convention`: using.html#windows-calling-conventions + + +v1.2.1 +------ + +Nothing changed from v1.2.0. + + +v1.2.0 +------ + +* Out-of-line mode: ``int a[][...];`` can be used to declare a structure + field or global variable which is, simultaneously, of total length + unknown to the C compiler (the ``a[]`` part) and each element is + itself an array of N integers, where the value of N *is* known to the + C compiler (the ``int`` and ``[...]`` parts around it). Similarly, + ``int a[5][...];`` is supported (but probably less useful: remember + that in C it means ``int (a[5])[...];``). + +* PyPy: the ``lib.some_function`` objects were missing the attributes + ``__name__``, ``__module__`` and ``__doc__`` that are expected e.g. by + some decorators-management functions from ``functools``. + +* Out-of-line API mode: you can now do ``from _example.lib import x`` + to import the name ``x`` from ``_example.lib``, even though the + ``lib`` object is not a standard module object. (Also works in ``from + _example.lib import *``, but this is even more of a hack and will fail + if ``lib`` happens to declare a name called ``__all__``. Note that + ``*`` excludes the global variables; only the functions and constants + make sense to import like this.) + +* ``lib.__dict__`` works again and gives you a copy of the + dict---assuming that ``lib`` has got no symbol called precisely + ``__dict__``. (In general, it is safer to use ``dir(lib)``.) + +* Out-of-line API mode: global variables are now fetched on demand at + every access. It fixes issue #212 (Windows DLL variables), and also + allows variables that are defined as dynamic macros (like ``errno``) + or ``__thread`` -local variables. (This change might also tighten + the C compiler's check on the variables' type.) + +* Issue #209: dereferencing NULL pointers now raises RuntimeError + instead of segfaulting. Meant as a debugging aid. The check is + only for NULL: if you dereference random or dead pointers you might + still get segfaults. + +* Issue #152: callbacks__: added an argument ``ffi.callback(..., + onerror=...)``. If the main callback function raises an exception + and ``onerror`` is provided, then ``onerror(exception, exc_value, + traceback)`` is called. This is similar to writing a ``try: + except:`` in the main callback function, but in some cases (e.g. a + signal) an exception can occur at the very start of the callback + function---before it had time to enter the ``try: except:`` block. + +* Issue #115: added ``ffi.new_allocator()``, which officializes + support for `alternative allocators`__. + +.. __: using.html#callbacks +.. __: ref.html#ffi-new-allocator + + +v1.1.2 +------ + +* ``ffi.gc()``: fixed a race condition in multithreaded programs + introduced in 1.1.1 + + +v1.1.1 +------ + +* Out-of-line mode: ``ffi.string()``, ``ffi.buffer()`` and + ``ffi.getwinerror()`` didn't accept their arguments as keyword + arguments, unlike their in-line mode equivalent. (It worked in PyPy.) + +* Out-of-line ABI mode: documented a restriction__ of ``ffi.dlopen()`` + when compared to the in-line mode. + +* ``ffi.gc()``: when called several times with equal pointers, it was + accidentally registering only the last destructor, or even none at + all depending on details. (It was correctly registering all of them + only in PyPy, and only with the out-of-line FFIs.) + +.. __: cdef.html#dlopen-note + + +v1.1.0 +------ + +* Out-of-line API mode: we can now declare integer types with + ``typedef int... foo_t;``. The exact size and signedness of ``foo_t`` + is figured out by the compiler. + +* Out-of-line API mode: we can now declare multidimensional arrays + (as fields or as globals) with ``int n[...][...]``. Before, only the + outermost dimension would support the ``...`` syntax. + +* Out-of-line ABI mode: we now support any constant declaration, + instead of only integers whose value is given in the cdef. Such "new" + constants, i.e. either non-integers or without a value given in the + cdef, must correspond to actual symbols in the lib. At runtime they + are looked up the first time we access them. This is useful if the + library defines ``extern const sometype somename;``. + +* ``ffi.addressof(lib, "func_name")`` now returns a regular cdata object + of type "pointer to function". You can use it on any function from a + library in API mode (in ABI mode, all functions are already regular + cdata objects). To support this, you need to recompile your cffi + modules. + +* Issue #198: in API mode, if you declare constants of a ``struct`` + type, what you saw from lib.CONSTANT was corrupted. + +* Issue #196: ``ffi.set_source("package._ffi", None)`` would + incorrectly generate the Python source to ``package._ffi.py`` instead + of ``package/_ffi.py``. Also fixed: in some cases, if the C file was + in ``build/foo.c``, the .o file would be put in ``build/build/foo.o``. + + +v1.0.3 +------ + +* Same as 1.0.2, apart from doc and test fixes on some platforms. + + +v1.0.2 +------ + +* Variadic C functions (ending in a "..." argument) were not supported + in the out-of-line ABI mode. This was a bug---there was even a + (non-working) example__ doing exactly that! + +.. __: overview.html#out-of-line-abi-level + + +v1.0.1 +------ + +* ``ffi.set_source()`` crashed if passed a ``sources=[..]`` argument. + Fixed by chrippa on pull request #60. + +* Issue #193: if we use a struct between the first cdef() where it is + declared and another cdef() where its fields are defined, then this + definition was ignored. + +* Enums were buggy if you used too many "..." in their definition. + + +v1.0.0 +------ + +* The main news item is out-of-line module generation: + + * `for ABI level`_, with ``ffi.dlopen()`` + + * `for API level`_, which used to be with ``ffi.verify()``, now deprecated + +* (this page will list what is new from all versions from 1.0.0 + forward.) + +.. _`for ABI level`: overview.html#out-of-line-abi-level +.. _`for API level`: overview.html#out-of-line-api-level diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a97f028 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pycparser +pytest diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0c9e0fc --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +license_file = LICENSE diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f980590 --- /dev/null +++ b/setup.py @@ -0,0 +1,250 @@ +import sys, os +import subprocess +import errno + +# on Windows we give up and always import setuptools early to fix things for us +if sys.platform == "win32": + import setuptools + + +sources = ['c/_cffi_backend.c'] +libraries = ['ffi'] +include_dirs = ['/usr/include/ffi', + '/usr/include/libffi'] # may be changed by pkg-config +define_macros = [] +library_dirs = [] +extra_compile_args = [] +extra_link_args = [] + + +def _ask_pkg_config(resultlist, option, result_prefix='', sysroot=False): + pkg_config = os.environ.get('PKG_CONFIG','pkg-config') + try: + p = subprocess.Popen([pkg_config, option, 'libffi'], + stdout=subprocess.PIPE) + except OSError as e: + if e.errno not in [errno.ENOENT, errno.EACCES]: + raise + else: + t = p.stdout.read().decode().strip() + p.stdout.close() + if p.wait() == 0: + res = t.split() + # '-I/usr/...' -> '/usr/...' + for x in res: + assert x.startswith(result_prefix) + res = [x[len(result_prefix):] for x in res] + #print 'PKG_CONFIG:', option, res + # + sysroot = sysroot and os.environ.get('PKG_CONFIG_SYSROOT_DIR', '') + if sysroot: + # old versions of pkg-config don't support this env var, + # so here we emulate its effect if needed + res = [path if path.startswith(sysroot) + else sysroot + path + for path in res] + # + resultlist[:] = res + +no_compiler_found = False +def no_working_compiler_found(): + sys.stderr.write(""" + No working compiler found, or bogus compiler options passed to + the compiler from Python's standard "distutils" module. See + the error messages above. Likely, the problem is not related + to CFFI but generic to the setup.py of any Python package that + tries to compile C code. (Hints: on OS/X 10.8, for errors about + -mno-fused-madd see http://stackoverflow.com/questions/22313407/ + Otherwise, see https://wiki.python.org/moin/CompLangPython or + the IRC channel #python on irc.freenode.net.) + + Trying to continue anyway. If you are trying to install CFFI from + a build done in a different context, you can ignore this warning. + \n""") + global no_compiler_found + no_compiler_found = True + +def get_config(): + from distutils.core import Distribution + from distutils.sysconfig import get_config_vars + get_config_vars() # workaround for a bug of distutils, e.g. on OS/X + config = Distribution().get_command_obj('config') + return config + +def ask_supports_thread(): + config = get_config() + ok = (sys.platform != 'win32' and + config.try_compile('__thread int some_threadlocal_variable_42;')) + if ok: + define_macros.append(('USE__THREAD', None)) + else: + ok1 = config.try_compile('int some_regular_variable_42;') + if not ok1: + no_working_compiler_found() + else: + sys.stderr.write("Note: will not use '__thread' in the C code\n") + _safe_to_ignore() + +def ask_supports_sync_synchronize(): + if sys.platform == 'win32' or no_compiler_found: + return + config = get_config() + ok = config.try_link('int main(void) { __sync_synchronize(); return 0; }') + if ok: + define_macros.append(('HAVE_SYNC_SYNCHRONIZE', None)) + else: + sys.stderr.write("Note: will not use '__sync_synchronize()'" + " in the C code\n") + _safe_to_ignore() + +def _safe_to_ignore(): + sys.stderr.write("***** The above error message can be safely ignored.\n\n") + +def uses_msvc(): + config = get_config() + return config.try_compile('#ifndef _MSC_VER\n#error "not MSVC"\n#endif') + +def use_pkg_config(): + if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'): + use_homebrew_for_libffi() + + _ask_pkg_config(include_dirs, '--cflags-only-I', '-I', sysroot=True) + _ask_pkg_config(extra_compile_args, '--cflags-only-other') + _ask_pkg_config(library_dirs, '--libs-only-L', '-L', sysroot=True) + _ask_pkg_config(extra_link_args, '--libs-only-other') + _ask_pkg_config(libraries, '--libs-only-l', '-l') + +def use_homebrew_for_libffi(): + # We can build by setting: + # PKG_CONFIG_PATH = $(brew --prefix libffi)/lib/pkgconfig + with os.popen('brew --prefix libffi') as brew_prefix_cmd: + prefix = brew_prefix_cmd.read().strip() + pkgconfig = os.path.join(prefix, 'lib', 'pkgconfig') + os.environ['PKG_CONFIG_PATH'] = ( + os.environ.get('PKG_CONFIG_PATH', '') + ':' + pkgconfig) + + +if sys.platform == 'win32' and uses_msvc(): + COMPILE_LIBFFI = 'c/libffi_msvc' # from the CPython distribution +else: + COMPILE_LIBFFI = None + +if COMPILE_LIBFFI: + assert os.path.isdir(COMPILE_LIBFFI), "directory not found!" + include_dirs[:] = [COMPILE_LIBFFI] + libraries[:] = [] + _filenames = [filename.lower() for filename in os.listdir(COMPILE_LIBFFI)] + _filenames = [filename for filename in _filenames + if filename.endswith('.c')] + if sys.maxsize > 2**32: + # 64-bit: unlist win32.c, and add instead win64.obj. If the obj + # happens to get outdated at some point in the future, you need to + # rebuild it manually from win64.asm. + _filenames.remove('win32.c') + extra_link_args.append(os.path.join(COMPILE_LIBFFI, 'win64.obj')) + sources.extend(os.path.join(COMPILE_LIBFFI, filename) + for filename in _filenames) +else: + use_pkg_config() + ask_supports_thread() + ask_supports_sync_synchronize() + +if 'freebsd' in sys.platform: + include_dirs.append('/usr/local/include') + library_dirs.append('/usr/local/lib') + +if 'darwin' in sys.platform: + try: + p = subprocess.Popen(['xcrun', '--show-sdk-path'], + stdout=subprocess.PIPE) + except OSError as e: + if e.errno not in [errno.ENOENT, errno.EACCES]: + raise + else: + t = p.stdout.read().decode().strip() + p.stdout.close() + if p.wait() == 0: + include_dirs.append(t + '/usr/include/ffi') + + + +if __name__ == '__main__': + from setuptools import setup, Distribution, Extension + + class CFFIDistribution(Distribution): + def has_ext_modules(self): + # Event if we don't have extension modules (e.g. on PyPy) we want to + # claim that we do so that wheels get properly tagged as Python + # specific. (thanks dstufft!) + return True + + # On PyPy, cffi is preinstalled and it is not possible, at least for now, + # to install a different version. We work around it by making the setup() + # arguments mostly empty in this case. + cpython = ('_cffi_backend' not in sys.builtin_module_names) + + setup( + name='cffi', + description='Foreign Function Interface for Python calling C code.', + long_description=""" +CFFI +==== + +Foreign Function Interface for Python calling C code. +Please see the `Documentation `_. + +Contact +------- + +`Mailing list `_ +""", + version='1.12.2', + packages=['cffi'] if cpython else [], + package_data={'cffi': ['_cffi_include.h', 'parse_c_type.h', + '_embedding.h', '_cffi_errors.h']} + if cpython else {}, + zip_safe=False, + + url='http://cffi.readthedocs.org', + author='Armin Rigo, Maciej Fijalkowski', + author_email='python-cffi@googlegroups.com', + + license='MIT', + + distclass=CFFIDistribution, + ext_modules=[Extension( + name='_cffi_backend', + include_dirs=include_dirs, + sources=sources, + libraries=libraries, + define_macros=define_macros, + library_dirs=library_dirs, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + )] if cpython else [], + + install_requires=[ + 'pycparser' if sys.version_info >= (2, 7) else 'pycparser<2.19', + ] if cpython else [], + + entry_points = { + "distutils.setup_keywords": [ + "cffi_modules = cffi.setuptools_ext:cffi_modules", + ], + }, + + classifiers=[ + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + ], + ) diff --git a/setup_base.py b/setup_base.py new file mode 100644 index 0000000..667c7d5 --- /dev/null +++ b/setup_base.py @@ -0,0 +1,22 @@ +import sys, os + + +from setup import include_dirs, sources, libraries, define_macros +from setup import library_dirs, extra_compile_args, extra_link_args + + +if __name__ == '__main__': + from distutils.core import setup + from distutils.extension import Extension + standard = '__pypy__' not in sys.builtin_module_names + setup(packages=['cffi'], + requires=['pycparser'], + ext_modules=[Extension(name = '_cffi_backend', + include_dirs=include_dirs, + sources=sources, + libraries=libraries, + define_macros=define_macros, + library_dirs=library_dirs, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + )] * standard) diff --git a/testing/__init__.py b/testing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/cffi0/__init__.py b/testing/cffi0/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/cffi0/backend_tests.py b/testing/cffi0/backend_tests.py new file mode 100644 index 0000000..13a4c78 --- /dev/null +++ b/testing/cffi0/backend_tests.py @@ -0,0 +1,1990 @@ +import py +import platform +import sys, ctypes +from cffi import FFI, CDefError, FFIError, VerificationMissing +from testing.support import * + +SIZE_OF_INT = ctypes.sizeof(ctypes.c_int) +SIZE_OF_LONG = ctypes.sizeof(ctypes.c_long) +SIZE_OF_SHORT = ctypes.sizeof(ctypes.c_short) +SIZE_OF_PTR = ctypes.sizeof(ctypes.c_void_p) +SIZE_OF_WCHAR = ctypes.sizeof(ctypes.c_wchar) + +def needs_dlopen_none(): + if sys.platform == 'win32' and sys.version_info >= (3,): + py.test.skip("dlopen(None) cannot work on Windows for Python 3") + + +class BackendTests: + + def test_integer_ranges(self): + ffi = FFI(backend=self.Backend()) + for (c_type, size) in [('char', 1), + ('short', 2), + ('short int', 2), + ('', 4), + ('int', 4), + ('long', SIZE_OF_LONG), + ('long int', SIZE_OF_LONG), + ('long long', 8), + ('long long int', 8), + ]: + for unsigned in [None, False, True]: + c_decl = {None: '', + False: 'signed ', + True: 'unsigned '}[unsigned] + c_type + if c_decl == 'char' or c_decl == '': + continue + self._test_int_type(ffi, c_decl, size, unsigned) + + def test_fixedsize_int(self): + ffi = FFI(backend=self.Backend()) + for size in [1, 2, 4, 8]: + self._test_int_type(ffi, 'int%d_t' % (8*size), size, False) + self._test_int_type(ffi, 'uint%d_t' % (8*size), size, True) + self._test_int_type(ffi, 'intptr_t', SIZE_OF_PTR, False) + self._test_int_type(ffi, 'uintptr_t', SIZE_OF_PTR, True) + self._test_int_type(ffi, 'ptrdiff_t', SIZE_OF_PTR, False) + self._test_int_type(ffi, 'size_t', SIZE_OF_PTR, True) + self._test_int_type(ffi, 'ssize_t', SIZE_OF_PTR, False) + + def _test_int_type(self, ffi, c_decl, size, unsigned): + if unsigned: + min = 0 + max = (1 << (8*size)) - 1 + else: + min = -(1 << (8*size-1)) + max = (1 << (8*size-1)) - 1 + min = int(min) + max = int(max) + p = ffi.cast(c_decl, min) + assert p == min + assert hash(p) == hash(min) + assert bool(p) is bool(min) + assert int(p) == min + p = ffi.cast(c_decl, max) + assert int(p) == max + p = ffi.cast(c_decl, long(max)) + assert int(p) == max + q = ffi.cast(c_decl, min - 1) + assert ffi.typeof(q) is ffi.typeof(p) and int(q) == max + q = ffi.cast(c_decl, long(min - 1)) + assert ffi.typeof(q) is ffi.typeof(p) and int(q) == max + assert q == p + assert int(q) == int(p) + assert hash(q) == hash(p) + c_decl_ptr = '%s *' % c_decl + py.test.raises(OverflowError, ffi.new, c_decl_ptr, min - 1) + py.test.raises(OverflowError, ffi.new, c_decl_ptr, max + 1) + py.test.raises(OverflowError, ffi.new, c_decl_ptr, long(min - 1)) + py.test.raises(OverflowError, ffi.new, c_decl_ptr, long(max + 1)) + assert ffi.new(c_decl_ptr, min)[0] == min + assert ffi.new(c_decl_ptr, max)[0] == max + assert ffi.new(c_decl_ptr, long(min))[0] == min + assert ffi.new(c_decl_ptr, long(max))[0] == max + + def test_new_unsupported_type(self): + ffi = FFI(backend=self.Backend()) + e = py.test.raises(TypeError, ffi.new, "int") + assert str(e.value) == "expected a pointer or array ctype, got 'int'" + + def test_new_single_integer(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int *") # similar to ffi.new("int[1]") + assert p[0] == 0 + p[0] = -123 + assert p[0] == -123 + p = ffi.new("int *", -42) + assert p[0] == -42 + assert repr(p) == "" % SIZE_OF_INT + + def test_new_array_no_arg(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int[10]") + # the object was zero-initialized: + for i in range(10): + assert p[i] == 0 + + def test_array_indexing(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int[10]") + p[0] = 42 + p[9] = 43 + assert p[0] == 42 + assert p[9] == 43 + py.test.raises(IndexError, "p[10]") + py.test.raises(IndexError, "p[10] = 44") + py.test.raises(IndexError, "p[-1]") + py.test.raises(IndexError, "p[-1] = 44") + + def test_new_array_args(self): + ffi = FFI(backend=self.Backend()) + # this tries to be closer to C: where we say "int x[5] = {10, 20, ..}" + # then here we must enclose the items in a list + p = ffi.new("int[5]", [10, 20, 30, 40, 50]) + assert p[0] == 10 + assert p[1] == 20 + assert p[2] == 30 + assert p[3] == 40 + assert p[4] == 50 + p = ffi.new("int[4]", [25]) + assert p[0] == 25 + assert p[1] == 0 # follow C convention rather than LuaJIT's + assert p[2] == 0 + assert p[3] == 0 + p = ffi.new("int[4]", [ffi.cast("int", -5)]) + assert p[0] == -5 + assert repr(p) == "" % (4*SIZE_OF_INT) + + def test_new_array_varsize(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int[]", 10) # a single integer is the length + assert p[9] == 0 + py.test.raises(IndexError, "p[10]") + # + py.test.raises(TypeError, ffi.new, "int[]") + # + p = ffi.new("int[]", [-6, -7]) # a list is all the items, like C + assert p[0] == -6 + assert p[1] == -7 + py.test.raises(IndexError, "p[2]") + assert repr(p) == "" % (2*SIZE_OF_INT) + # + p = ffi.new("int[]", 0) + py.test.raises(IndexError, "p[0]") + py.test.raises(ValueError, ffi.new, "int[]", -1) + assert repr(p) == "" + + def test_pointer_init(self): + ffi = FFI(backend=self.Backend()) + n = ffi.new("int *", 24) + a = ffi.new("int *[10]", [ffi.NULL, ffi.NULL, n, n, ffi.NULL]) + for i in range(10): + if i not in (2, 3): + assert a[i] == ffi.NULL + assert a[2] == a[3] == n + + def test_cannot_cast(self): + ffi = FFI(backend=self.Backend()) + a = ffi.new("short int[10]") + e = py.test.raises(TypeError, ffi.new, "long int **", a) + msg = str(e.value) + assert "'short[10]'" in msg and "'long *'" in msg + + def test_new_pointer_to_array(self): + ffi = FFI(backend=self.Backend()) + a = ffi.new("int[4]", [100, 102, 104, 106]) + p = ffi.new("int **", a) + assert p[0] == ffi.cast("int *", a) + assert p[0][2] == 104 + p = ffi.cast("int *", a) + assert p[0] == 100 + assert p[1] == 102 + assert p[2] == 104 + assert p[3] == 106 + # keepalive: a + + def test_pointer_direct(self): + ffi = FFI(backend=self.Backend()) + p = ffi.cast("int*", 0) + assert p is not None + assert bool(p) is False + assert p == ffi.cast("int*", 0) + assert p != None + assert repr(p) == "" + a = ffi.new("int[]", [123, 456]) + p = ffi.cast("int*", a) + assert bool(p) is True + assert p == ffi.cast("int*", a) + assert p != ffi.cast("int*", 0) + assert p[0] == 123 + assert p[1] == 456 + + def test_repr(self): + typerepr = self.TypeRepr + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { short a, b, c; };") + p = ffi.cast("short unsigned int", 0) + assert repr(p) == "" + assert repr(ffi.typeof(p)) == typerepr % "unsigned short" + p = ffi.cast("unsigned short int", 0) + assert repr(p) == "" + assert repr(ffi.typeof(p)) == typerepr % "unsigned short" + p = ffi.cast("int*", 0) + assert repr(p) == "" + assert repr(ffi.typeof(p)) == typerepr % "int *" + # + p = ffi.new("int*") + assert repr(p) == "" % SIZE_OF_INT + assert repr(ffi.typeof(p)) == typerepr % "int *" + p = ffi.new("int**") + assert repr(p) == "" % SIZE_OF_PTR + assert repr(ffi.typeof(p)) == typerepr % "int * *" + p = ffi.new("int [2]") + assert repr(p) == "" % (2*SIZE_OF_INT) + assert repr(ffi.typeof(p)) == typerepr % "int[2]" + p = ffi.new("int*[2][3]") + assert repr(p) == "" % ( + 6*SIZE_OF_PTR) + assert repr(ffi.typeof(p)) == typerepr % "int *[2][3]" + p = ffi.new("struct foo *") + assert repr(p) == "" % ( + 3*SIZE_OF_SHORT) + assert repr(ffi.typeof(p)) == typerepr % "struct foo *" + # + q = ffi.cast("short", -123) + assert repr(q) == "" + assert repr(ffi.typeof(q)) == typerepr % "short" + p = ffi.new("int*") + q = ffi.cast("short*", p) + assert repr(q).startswith(" 2: + assert ffi.new("wchar_t*", u+'\U00012345')[0] == u+'\U00012345' + else: + py.test.raises(TypeError, ffi.new, "wchar_t*", u+'\U00012345') + assert ffi.new("wchar_t*")[0] == u+'\x00' + assert int(ffi.cast("wchar_t", 300)) == 300 + assert not bool(ffi.cast("wchar_t", 0)) + assert bool(ffi.cast("wchar_t", 1)) + assert bool(ffi.cast("wchar_t", 65535)) + if SIZE_OF_WCHAR > 2: + assert bool(ffi.cast("wchar_t", 65536)) + py.test.raises(TypeError, ffi.new, "wchar_t*", 32) + py.test.raises(TypeError, ffi.new, "wchar_t*", "foo") + # + p = ffi.new("wchar_t[]", [u+'a', u+'b', u+'\u1234']) + assert len(p) == 3 + assert p[0] == u+'a' + assert p[1] == u+'b' and type(p[1]) is unicode + assert p[2] == u+'\u1234' + p[0] = u+'x' + assert p[0] == u+'x' and type(p[0]) is unicode + p[1] = u+'\u1357' + assert p[1] == u+'\u1357' + p = ffi.new("wchar_t[]", u+"abcd") + assert len(p) == 5 + assert p[4] == u+'\x00' + p = ffi.new("wchar_t[]", u+"a\u1234b") + assert len(p) == 4 + assert p[1] == u+'\u1234' + # + p = ffi.new("wchar_t[]", u+'\U00023456') + if SIZE_OF_WCHAR == 2: + assert len(p) == 3 + assert p[0] == u+'\ud84d' + assert p[1] == u+'\udc56' + assert p[2] == u+'\x00' + else: + assert len(p) == 2 + assert p[0] == u+'\U00023456' + assert p[1] == u+'\x00' + # + p = ffi.new("wchar_t[4]", u+"ab") + assert len(p) == 4 + assert [p[i] for i in range(4)] == [u+'a', u+'b', u+'\x00', u+'\x00'] + p = ffi.new("wchar_t[2]", u+"ab") + assert len(p) == 2 + assert [p[i] for i in range(2)] == [u+'a', u+'b'] + py.test.raises(IndexError, ffi.new, "wchar_t[2]", u+"abc") + + def test_none_as_null_doesnt_work(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int*[1]") + assert p[0] is not None + assert p[0] != None + assert p[0] == ffi.NULL + assert repr(p[0]) == "" + # + n = ffi.new("int*", 99) + p = ffi.new("int*[]", [n]) + assert p[0][0] == 99 + py.test.raises(TypeError, "p[0] = None") + p[0] = ffi.NULL + assert p[0] == ffi.NULL + + def test_float(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("float[]", [-2, -2.5]) + assert p[0] == -2.0 + assert p[1] == -2.5 + p[1] += 17.75 + assert p[1] == 15.25 + # + p = ffi.new("float*", 15.75) + assert p[0] == 15.75 + py.test.raises(TypeError, int, p) + py.test.raises(TypeError, float, p) + p[0] = 0.0 + assert bool(p) is True + # + p = ffi.new("float*", 1.1) + f = p[0] + assert f != 1.1 # because of rounding effect + assert abs(f - 1.1) < 1E-7 + # + INF = 1E200 * 1E200 + assert 1E200 != INF + p[0] = 1E200 + assert p[0] == INF # infinite, not enough precision + + def test_struct_simple(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a; short b, c; };") + s = ffi.new("struct foo*") + assert s.a == s.b == s.c == 0 + s.b = -23 + assert s.b == -23 + py.test.raises(OverflowError, "s.b = 32768") + # + s = ffi.new("struct foo*", [-2, -3]) + assert s.a == -2 + assert s.b == -3 + assert s.c == 0 + py.test.raises((AttributeError, TypeError), "del s.a") + assert repr(s) == "" % ( + SIZE_OF_INT + 2 * SIZE_OF_SHORT) + # + py.test.raises(ValueError, ffi.new, "struct foo*", [1, 2, 3, 4]) + + def test_constructor_struct_from_dict(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a; short b, c; };") + s = ffi.new("struct foo*", {'b': 123, 'c': 456}) + assert s.a == 0 + assert s.b == 123 + assert s.c == 456 + py.test.raises(KeyError, ffi.new, "struct foo*", {'d': 456}) + + def test_struct_pointer(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a; short b, c; };") + s = ffi.new("struct foo*") + assert s[0].a == s[0].b == s[0].c == 0 + s[0].b = -23 + assert s[0].b == s.b == -23 + py.test.raises(OverflowError, "s[0].b = -32769") + py.test.raises(IndexError, "s[1]") + + def test_struct_opaque(self): + ffi = FFI(backend=self.Backend()) + py.test.raises(TypeError, ffi.new, "struct baz*") + p = ffi.new("struct baz **") # this works + assert p[0] == ffi.NULL + + def test_pointer_to_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a; short b, c; };") + s = ffi.new("struct foo *") + s.a = -42 + assert s[0].a == -42 + p = ffi.new("struct foo **", s) + assert p[0].a == -42 + assert p[0][0].a == -42 + p[0].a = -43 + assert s.a == -43 + assert s[0].a == -43 + p[0][0].a = -44 + assert s.a == -44 + assert s[0].a == -44 + s.a = -45 + assert p[0].a == -45 + assert p[0][0].a == -45 + s[0].a = -46 + assert p[0].a == -46 + assert p[0][0].a == -46 + + def test_constructor_struct_of_array(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a[2]; char b[3]; };") + s = ffi.new("struct foo *", [[10, 11], [b'a', b'b', b'c']]) + assert s.a[1] == 11 + assert s.b[2] == b'c' + s.b[1] = b'X' + assert s.b[0] == b'a' + assert s.b[1] == b'X' + assert s.b[2] == b'c' + + def test_recursive_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int value; struct foo *next; };") + s = ffi.new("struct foo*") + t = ffi.new("struct foo*") + s.value = 123 + s.next = t + t.value = 456 + assert s.value == 123 + assert s.next.value == 456 + + def test_union_simple(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("union foo { int a; short b, c; };") + u = ffi.new("union foo*") + assert u.a == u.b == u.c == 0 + u.b = -23 + assert u.b == -23 + assert u.a != 0 + py.test.raises(OverflowError, "u.b = 32768") + # + u = ffi.new("union foo*", [-2]) + assert u.a == -2 + py.test.raises((AttributeError, TypeError), "del u.a") + assert repr(u) == "" % SIZE_OF_INT + + def test_union_opaque(self): + ffi = FFI(backend=self.Backend()) + py.test.raises(TypeError, ffi.new, "union baz *") + u = ffi.new("union baz **") # this works + assert u[0] == ffi.NULL + + def test_union_initializer(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("union foo { char a; int b; };") + py.test.raises(TypeError, ffi.new, "union foo*", b'A') + py.test.raises(TypeError, ffi.new, "union foo*", 5) + py.test.raises(ValueError, ffi.new, "union foo*", [b'A', 5]) + u = ffi.new("union foo*", [b'A']) + assert u.a == b'A' + py.test.raises(TypeError, ffi.new, "union foo*", [1005]) + u = ffi.new("union foo*", {'b': 12345}) + assert u.b == 12345 + u = ffi.new("union foo*", []) + assert u.a == b'\x00' + assert u.b == 0 + + def test_sizeof_type(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + struct foo { int a; short b, c, d; }; + union foo { int a; short b, c, d; }; + """) + for c_type, expected_size in [ + ('char', 1), + ('unsigned int', 4), + ('char *', SIZE_OF_PTR), + ('int[5]', 20), + ('struct foo', 12), + ('union foo', 4), + ]: + size = ffi.sizeof(c_type) + assert size == expected_size, (size, expected_size, ctype) + + def test_sizeof_cdata(self): + ffi = FFI(backend=self.Backend()) + assert ffi.sizeof(ffi.new("short*")) == SIZE_OF_PTR + assert ffi.sizeof(ffi.cast("short", 123)) == SIZE_OF_SHORT + # + a = ffi.new("int[]", [10, 11, 12, 13, 14]) + assert len(a) == 5 + assert ffi.sizeof(a) == 5 * SIZE_OF_INT + + def test_string_from_char_pointer(self): + ffi = FFI(backend=self.Backend()) + x = ffi.new("char*", b"x") + assert str(x) == repr(x) + assert ffi.string(x) == b"x" + assert ffi.string(ffi.new("char*", b"\x00")) == b"" + py.test.raises(TypeError, ffi.new, "char*", unicode("foo")) + + def test_unicode_from_wchar_pointer(self): + ffi = FFI(backend=self.Backend()) + self.check_wchar_t(ffi) + x = ffi.new("wchar_t*", u+"x") + assert unicode(x) == unicode(repr(x)) + assert ffi.string(x) == u+"x" + assert ffi.string(ffi.new("wchar_t*", u+"\x00")) == u+"" + + def test_string_from_char_array(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("char[]", b"hello.") + p[5] = b'!' + assert ffi.string(p) == b"hello!" + p[6] = b'?' + assert ffi.string(p) == b"hello!?" + p[3] = b'\x00' + assert ffi.string(p) == b"hel" + assert ffi.string(p, 2) == b"he" + py.test.raises(IndexError, "p[7] = b'X'") + # + a = ffi.new("char[]", b"hello\x00world") + assert len(a) == 12 + p = ffi.cast("char *", a) + assert ffi.string(p) == b'hello' + + def test_string_from_wchar_array(self): + ffi = FFI(backend=self.Backend()) + self.check_wchar_t(ffi) + assert ffi.string(ffi.cast("wchar_t", "x")) == u+"x" + assert ffi.string(ffi.cast("wchar_t", u+"x")) == u+"x" + x = ffi.cast("wchar_t", "x") + assert str(x) == repr(x) + assert ffi.string(x) == u+"x" + # + p = ffi.new("wchar_t[]", u+"hello.") + p[5] = u+'!' + assert ffi.string(p) == u+"hello!" + p[6] = u+'\u04d2' + assert ffi.string(p) == u+"hello!\u04d2" + p[3] = u+'\x00' + assert ffi.string(p) == u+"hel" + assert ffi.string(p, 123) == u+"hel" + py.test.raises(IndexError, "p[7] = u+'X'") + # + a = ffi.new("wchar_t[]", u+"hello\x00world") + assert len(a) == 12 + p = ffi.cast("wchar_t *", a) + assert ffi.string(p) == u+'hello' + assert ffi.string(p, 123) == u+'hello' + assert ffi.string(p, 5) == u+'hello' + assert ffi.string(p, 2) == u+'he' + + def test_fetch_const_char_p_field(self): + # 'const' is ignored so far + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { const char *name; };") + t = ffi.new("const char[]", b"testing") + s = ffi.new("struct foo*", [t]) + assert type(s.name) not in (bytes, str, unicode) + assert ffi.string(s.name) == b"testing" + py.test.raises(TypeError, "s.name = None") + s.name = ffi.NULL + assert s.name == ffi.NULL + + def test_fetch_const_wchar_p_field(self): + # 'const' is ignored so far + ffi = FFI(backend=self.Backend()) + self.check_wchar_t(ffi) + ffi.cdef("struct foo { const wchar_t *name; };") + t = ffi.new("const wchar_t[]", u+"testing") + s = ffi.new("struct foo*", [t]) + assert type(s.name) not in (bytes, str, unicode) + assert ffi.string(s.name) == u+"testing" + s.name = ffi.NULL + assert s.name == ffi.NULL + + def test_voidp(self): + ffi = FFI(backend=self.Backend()) + py.test.raises(TypeError, ffi.new, "void*") + p = ffi.new("void **") + assert p[0] == ffi.NULL + a = ffi.new("int[]", [10, 11, 12]) + p = ffi.new("void **", a) + vp = p[0] + py.test.raises(TypeError, "vp[0]") + py.test.raises(TypeError, ffi.new, "short **", a) + # + ffi.cdef("struct foo { void *p; int *q; short *r; };") + s = ffi.new("struct foo *") + s.p = a # works + s.q = a # works + py.test.raises(TypeError, "s.r = a") # fails + b = ffi.cast("int *", a) + s.p = b # works + s.q = b # works + py.test.raises(TypeError, "s.r = b") # fails + + def test_functionptr_simple(self): + ffi = FFI(backend=self.Backend()) + py.test.raises(TypeError, ffi.callback, "int(*)(int)", 0) + def cb(n): + return n + 1 + cb.__qualname__ = 'cb' + p = ffi.callback("int(*)(int)", cb) + res = p(41) # calling an 'int(*)(int)', i.e. a function pointer + assert res == 42 and type(res) is int + res = p(ffi.cast("int", -41)) + assert res == -40 and type(res) is int + assert repr(p).startswith( + "" % ( + SIZE_OF_PTR) + py.test.raises(TypeError, "q(43)") + res = q[0](43) + assert res == 44 + q = ffi.cast("int(*)(int)", p) + assert repr(q).startswith("" + assert repr(ffi.cast("enum foo", -1)) == ( # enums are unsigned, if + "") # they contain no neg value + ffi.cdef("enum baz { A2=0x1000, B2=0x2000 };") + assert ffi.string(ffi.cast("enum baz", 0x1000)) == "A2" + assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B2" + + def test_enum_in_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("enum foo { A, B, C, D }; struct bar { enum foo e; };") + s = ffi.new("struct bar *") + s.e = 0 + assert s.e == 0 + s.e = 3 + assert s.e == 3 + assert s[0].e == 3 + s[0].e = 2 + assert s.e == 2 + assert s[0].e == 2 + s.e = ffi.cast("enum foo", -1) + assert s.e == 4294967295 + assert s[0].e == 4294967295 + s.e = s.e + py.test.raises(TypeError, "s.e = 'B'") + py.test.raises(TypeError, "s.e = '2'") + py.test.raises(TypeError, "s.e = '#2'") + py.test.raises(TypeError, "s.e = '#7'") + + def test_enum_non_contiguous(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("enum foo { A, B=42, C };") + assert ffi.string(ffi.cast("enum foo", 0)) == "A" + assert ffi.string(ffi.cast("enum foo", 42)) == "B" + assert ffi.string(ffi.cast("enum foo", 43)) == "C" + invalid_value = ffi.cast("enum foo", 2) + assert int(invalid_value) == 2 + assert ffi.string(invalid_value) == "2" + + def test_enum_char_hex_oct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(r"enum foo{A='!', B='\'', C=0x10, D=010, E=- 0x10, F=-010};") + assert ffi.string(ffi.cast("enum foo", ord('!'))) == "A" + assert ffi.string(ffi.cast("enum foo", ord("'"))) == "B" + assert ffi.string(ffi.cast("enum foo", 16)) == "C" + assert ffi.string(ffi.cast("enum foo", 8)) == "D" + assert ffi.string(ffi.cast("enum foo", -16)) == "E" + assert ffi.string(ffi.cast("enum foo", -8)) == "F" + + def test_enum_partial(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(r"enum foo {A, ...}; enum bar { B, C };") + needs_dlopen_none() + lib = ffi.dlopen(None) + assert lib.B == 0 + py.test.raises(VerificationMissing, getattr, lib, "A") + assert lib.C == 1 + + def test_array_of_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a, b; };") + s = ffi.new("struct foo[1]") + py.test.raises(AttributeError, 's.b') + py.test.raises(AttributeError, 's.b = 412') + s[0].b = 412 + assert s[0].b == 412 + py.test.raises(IndexError, 's[1]') + + def test_pointer_to_array(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int(**)[5]") + assert repr(p) == "" % SIZE_OF_PTR + + def test_iterate_array(self): + ffi = FFI(backend=self.Backend()) + a = ffi.new("char[]", b"hello") + assert list(a) == [b"h", b"e", b"l", b"l", b"o", b"\0"] + assert list(iter(a)) == [b"h", b"e", b"l", b"l", b"o", b"\0"] + # + py.test.raises(TypeError, iter, ffi.cast("char *", a)) + py.test.raises(TypeError, list, ffi.cast("char *", a)) + py.test.raises(TypeError, iter, ffi.new("int *")) + py.test.raises(TypeError, list, ffi.new("int *")) + + def test_offsetof(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a, b, c; };") + assert ffi.offsetof("struct foo", "a") == 0 + assert ffi.offsetof("struct foo", "b") == 4 + assert ffi.offsetof("struct foo", "c") == 8 + + def test_offsetof_nested(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a, b, c; };" + "struct bar { struct foo d, e; };") + assert ffi.offsetof("struct bar", "e") == 12 + py.test.raises(KeyError, ffi.offsetof, "struct bar", "e.a") + assert ffi.offsetof("struct bar", "e", "a") == 12 + assert ffi.offsetof("struct bar", "e", "b") == 16 + assert ffi.offsetof("struct bar", "e", "c") == 20 + + def test_offsetof_array(self): + ffi = FFI(backend=self.Backend()) + assert ffi.offsetof("int[]", 51) == 51 * ffi.sizeof("int") + assert ffi.offsetof("int *", 51) == 51 * ffi.sizeof("int") + ffi.cdef("struct bar { int a, b; int c[99]; };") + assert ffi.offsetof("struct bar", "c") == 2 * ffi.sizeof("int") + assert ffi.offsetof("struct bar", "c", 0) == 2 * ffi.sizeof("int") + assert ffi.offsetof("struct bar", "c", 51) == 53 * ffi.sizeof("int") + + def test_alignof(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { char a; short b; char c; };") + assert ffi.alignof("int") == 4 + assert ffi.alignof("double") in (4, 8) + assert ffi.alignof("struct foo") == 2 + + def test_bitfield(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo { int a:10, b:20, c:3; };") + assert ffi.sizeof("struct foo") == 8 + s = ffi.new("struct foo *") + s.a = 511 + py.test.raises(OverflowError, "s.a = 512") + py.test.raises(OverflowError, "s[0].a = 512") + assert s.a == 511 + s.a = -512 + py.test.raises(OverflowError, "s.a = -513") + py.test.raises(OverflowError, "s[0].a = -513") + assert s.a == -512 + s.c = 3 + assert s.c == 3 + py.test.raises(OverflowError, "s.c = 4") + py.test.raises(OverflowError, "s[0].c = 4") + s.c = -4 + assert s.c == -4 + + def test_bitfield_enum(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef enum { AA, BB, CC } foo_e; + typedef struct { foo_e f:2; } foo_s; + """) + s = ffi.new("foo_s *") + s.f = 2 + assert s.f == 2 + + def test_anonymous_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("typedef struct { int a; } foo_t;") + ffi.cdef("typedef struct { char b, c; } bar_t;") + f = ffi.new("foo_t *", [12345]) + b = ffi.new("bar_t *", [b"B", b"C"]) + assert f.a == 12345 + assert b.b == b"B" + assert b.c == b"C" + assert repr(b).startswith(" s) is False + assert (p >= s) is True + assert (s < p) is False + assert (s <= p) is True + assert (s == p) is True + assert (s != p) is False + assert (s > p) is False + assert (s >= p) is True + q = p + 1 + assert (q < s) is False + assert (q <= s) is False + assert (q == s) is False + assert (q != s) is True + assert (q > s) is True + assert (q >= s) is True + assert (s < q) is True + assert (s <= q) is True + assert (s == q) is False + assert (s != q) is True + assert (s > q) is False + assert (s >= q) is False + assert (q < p) is False + assert (q <= p) is False + assert (q == p) is False + assert (q != p) is True + assert (q > p) is True + assert (q >= p) is True + assert (p < q) is True + assert (p <= q) is True + assert (p == q) is False + assert (p != q) is True + assert (p > q) is False + assert (p >= q) is False + # + assert (None == s) is False + assert (None != s) is True + assert (s == None) is False + assert (s != None) is True + assert (None == q) is False + assert (None != q) is True + assert (q == None) is False + assert (q != None) is True + + def test_integer_comparison(self): + ffi = FFI(backend=self.Backend()) + x = ffi.cast("int", 123) + y = ffi.cast("int", 456) + assert x < y + # + z = ffi.cast("double", 78.9) + assert x > z + assert y > z + + def test_ffi_buffer_ptr(self): + ffi = FFI(backend=self.Backend()) + a = ffi.new("short *", 100) + try: + b = ffi.buffer(a) + except NotImplementedError as e: + py.test.skip(str(e)) + assert type(b) is ffi.buffer + content = b[:] + assert len(content) == len(b) == 2 + if sys.byteorder == 'little': + assert content == b'\x64\x00' + assert b[0] == b'\x64' + b[0] = b'\x65' + else: + assert content == b'\x00\x64' + assert b[1] == b'\x64' + b[1] = b'\x65' + assert a[0] == 101 + + def test_ffi_buffer_array(self): + ffi = FFI(backend=self.Backend()) + a = ffi.new("int[]", list(range(100, 110))) + try: + b = ffi.buffer(a) + except NotImplementedError as e: + py.test.skip(str(e)) + content = b[:] + if sys.byteorder == 'little': + assert content.startswith(b'\x64\x00\x00\x00\x65\x00\x00\x00') + b[4] = b'\x45' + else: + assert content.startswith(b'\x00\x00\x00\x64\x00\x00\x00\x65') + b[7] = b'\x45' + assert len(content) == 4 * 10 + assert a[1] == 0x45 + + def test_ffi_buffer_ptr_size(self): + ffi = FFI(backend=self.Backend()) + a = ffi.new("short *", 0x4243) + try: + b = ffi.buffer(a, 1) + except NotImplementedError as e: + py.test.skip(str(e)) + content = b[:] + assert len(content) == 1 + if sys.byteorder == 'little': + assert content == b'\x43' + b[0] = b'\x62' + assert a[0] == 0x4262 + else: + assert content == b'\x42' + b[0] = b'\x63' + assert a[0] == 0x6343 + + def test_ffi_buffer_array_size(self): + ffi = FFI(backend=self.Backend()) + a1 = ffi.new("int[]", list(range(100, 110))) + a2 = ffi.new("int[]", list(range(100, 115))) + try: + ffi.buffer(a1) + except NotImplementedError as e: + py.test.skip(str(e)) + assert ffi.buffer(a1)[:] == ffi.buffer(a2, 4*10)[:] + + def test_ffi_buffer_with_file(self): + ffi = FFI(backend=self.Backend()) + import tempfile, os, array + fd, filename = tempfile.mkstemp() + f = os.fdopen(fd, 'r+b') + a = ffi.new("int[]", list(range(1005))) + try: + ffi.buffer(a, 512) + except NotImplementedError as e: + py.test.skip(str(e)) + f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) + f.seek(0) + assert f.read() == array.array('i', range(1000)).tostring() + f.seek(0) + b = ffi.new("int[]", 1005) + f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) + assert list(a)[:1000] + [0] * (len(a)-1000) == list(b) + f.close() + os.unlink(filename) + + def test_ffi_buffer_with_io(self): + ffi = FFI(backend=self.Backend()) + import io, array + f = io.BytesIO() + a = ffi.new("int[]", list(range(1005))) + try: + ffi.buffer(a, 512) + except NotImplementedError as e: + py.test.skip(str(e)) + f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) + f.seek(0) + assert f.read() == array.array('i', range(1000)).tostring() + f.seek(0) + b = ffi.new("int[]", 1005) + f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) + assert list(a)[:1000] + [0] * (len(a)-1000) == list(b) + f.close() + + def test_ffi_buffer_comparisons(self): + ffi = FFI(backend=self.Backend()) + ba = bytearray(range(100, 110)) + if sys.version_info >= (2, 7): + assert ba == memoryview(ba) # justification for the following + a = ffi.new("uint8_t[]", list(ba)) + c = ffi.new("uint8_t[]", [99] + list(ba)) + try: + b_full = ffi.buffer(a) + b_short = ffi.buffer(a, 3) + b_mid = ffi.buffer(a, 6) + b_other = ffi.buffer(c, 6) + except NotImplementedError as e: + py.test.skip(str(e)) + else: + content = b_full[:] + assert content == b_full == ba + assert b_other < b_short < b_mid < b_full + assert ba > b_mid > ba[0:2] + assert b_short != ba[1:4] + + def test_array_in_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo_s { int len; short data[5]; };") + p = ffi.new("struct foo_s *") + p.data[3] = 5 + assert p.data[3] == 5 + assert repr(p.data).startswith(" + typedef int (*mycallback_func_t)(int, int); + void *my_wait_function(void *ptr) { + mycallback_func_t cbfunc = (mycallback_func_t)ptr; + cbfunc(10, 10); + cbfunc(12, 15); + return NULL; + } + int threaded_ballback_test(mycallback_func_t mycb) { + pthread_t thread; + pthread_create(&thread, NULL, my_wait_function, (void*)mycb); + return 0; + } + """, extra_compile_args=['-pthread']) + seen = [] + @ffi.callback('int(*)(int,int)') + def mycallback(x, y): + time.sleep(0.022) + seen.append((x, y)) + return 0 + lib.threaded_ballback_test(mycallback) + count = 300 + while len(seen) != 2: + time.sleep(0.01) + count -= 1 + assert count > 0, "timeout" + assert seen == [(10, 10), (12, 15)] + +print('STARTING') +_run_callback_in_thread() +print('DONE') diff --git a/testing/cffi0/snippets/distutils_module/setup.py b/testing/cffi0/snippets/distutils_module/setup.py new file mode 100644 index 0000000..a4d5551 --- /dev/null +++ b/testing/cffi0/snippets/distutils_module/setup.py @@ -0,0 +1,7 @@ + +from distutils.core import setup +import snip_basic_verify + +setup( + py_modules=['snip_basic_verify'], + ext_modules=[snip_basic_verify.ffi.verifier.get_extension()]) diff --git a/testing/cffi0/snippets/distutils_module/snip_basic_verify.py b/testing/cffi0/snippets/distutils_module/snip_basic_verify.py new file mode 100644 index 0000000..e8a867e --- /dev/null +++ b/testing/cffi0/snippets/distutils_module/snip_basic_verify.py @@ -0,0 +1,17 @@ + +from cffi import FFI +import sys + +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") +C = ffi.verify(""" // passed to the real C compiler +#include +#include +""", libraries=[], # or a list of libraries to link with + force_generic_engine=hasattr(sys, '_force_generic_engine_')) diff --git a/testing/cffi0/snippets/distutils_package_1/setup.py b/testing/cffi0/snippets/distutils_package_1/setup.py new file mode 100644 index 0000000..e3d28a5 --- /dev/null +++ b/testing/cffi0/snippets/distutils_package_1/setup.py @@ -0,0 +1,7 @@ + +from distutils.core import setup +import snip_basic_verify1 + +setup( + packages=['snip_basic_verify1'], + ext_modules=[snip_basic_verify1.ffi.verifier.get_extension()]) diff --git a/testing/cffi0/snippets/distutils_package_1/snip_basic_verify1/__init__.py b/testing/cffi0/snippets/distutils_package_1/snip_basic_verify1/__init__.py new file mode 100644 index 0000000..e8a867e --- /dev/null +++ b/testing/cffi0/snippets/distutils_package_1/snip_basic_verify1/__init__.py @@ -0,0 +1,17 @@ + +from cffi import FFI +import sys + +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") +C = ffi.verify(""" // passed to the real C compiler +#include +#include +""", libraries=[], # or a list of libraries to link with + force_generic_engine=hasattr(sys, '_force_generic_engine_')) diff --git a/testing/cffi0/snippets/distutils_package_2/setup.py b/testing/cffi0/snippets/distutils_package_2/setup.py new file mode 100644 index 0000000..6d8f72a --- /dev/null +++ b/testing/cffi0/snippets/distutils_package_2/setup.py @@ -0,0 +1,8 @@ + +from distutils.core import setup +import snip_basic_verify2 + +setup( + packages=['snip_basic_verify2'], + ext_package='snip_basic_verify2', + ext_modules=[snip_basic_verify2.ffi.verifier.get_extension()]) diff --git a/testing/cffi0/snippets/distutils_package_2/snip_basic_verify2/__init__.py b/testing/cffi0/snippets/distutils_package_2/snip_basic_verify2/__init__.py new file mode 100644 index 0000000..b4ee686 --- /dev/null +++ b/testing/cffi0/snippets/distutils_package_2/snip_basic_verify2/__init__.py @@ -0,0 +1,18 @@ + +from cffi import FFI +import sys + +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") +C = ffi.verify(""" // passed to the real C compiler +#include +#include +""", libraries=[], # or a list of libraries to link with + ext_package='snip_basic_verify2', + force_generic_engine=hasattr(sys, '_force_generic_engine_')) diff --git a/testing/cffi0/snippets/infrastructure/setup.py b/testing/cffi0/snippets/infrastructure/setup.py new file mode 100644 index 0000000..ea89f50 --- /dev/null +++ b/testing/cffi0/snippets/infrastructure/setup.py @@ -0,0 +1,5 @@ + +from distutils.core import setup + +setup(packages=['snip_infrastructure'], + requires=['cffi']) diff --git a/testing/cffi0/snippets/infrastructure/snip_infrastructure/__init__.py b/testing/cffi0/snippets/infrastructure/snip_infrastructure/__init__.py new file mode 100644 index 0000000..dad950d --- /dev/null +++ b/testing/cffi0/snippets/infrastructure/snip_infrastructure/__init__.py @@ -0,0 +1,3 @@ + +def func(): + return 42 diff --git a/testing/cffi0/snippets/setuptools_module/setup.py b/testing/cffi0/snippets/setuptools_module/setup.py new file mode 100644 index 0000000..30f2e04 --- /dev/null +++ b/testing/cffi0/snippets/setuptools_module/setup.py @@ -0,0 +1,8 @@ + +from setuptools import setup +import snip_setuptools_verify + +setup( + zip_safe=False, + py_modules=['snip_setuptools_verify'], + ext_modules=[snip_setuptools_verify.ffi.verifier.get_extension()]) diff --git a/testing/cffi0/snippets/setuptools_module/snip_setuptools_verify.py b/testing/cffi0/snippets/setuptools_module/snip_setuptools_verify.py new file mode 100644 index 0000000..e8a867e --- /dev/null +++ b/testing/cffi0/snippets/setuptools_module/snip_setuptools_verify.py @@ -0,0 +1,17 @@ + +from cffi import FFI +import sys + +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") +C = ffi.verify(""" // passed to the real C compiler +#include +#include +""", libraries=[], # or a list of libraries to link with + force_generic_engine=hasattr(sys, '_force_generic_engine_')) diff --git a/testing/cffi0/snippets/setuptools_package_1/setup.py b/testing/cffi0/snippets/setuptools_package_1/setup.py new file mode 100644 index 0000000..18ea3f6 --- /dev/null +++ b/testing/cffi0/snippets/setuptools_package_1/setup.py @@ -0,0 +1,8 @@ + +from setuptools import setup +import snip_setuptools_verify1 + +setup( + zip_safe=False, + packages=['snip_setuptools_verify1'], + ext_modules=[snip_setuptools_verify1.ffi.verifier.get_extension()]) diff --git a/testing/cffi0/snippets/setuptools_package_1/snip_setuptools_verify1/__init__.py b/testing/cffi0/snippets/setuptools_package_1/snip_setuptools_verify1/__init__.py new file mode 100644 index 0000000..e8a867e --- /dev/null +++ b/testing/cffi0/snippets/setuptools_package_1/snip_setuptools_verify1/__init__.py @@ -0,0 +1,17 @@ + +from cffi import FFI +import sys + +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") +C = ffi.verify(""" // passed to the real C compiler +#include +#include +""", libraries=[], # or a list of libraries to link with + force_generic_engine=hasattr(sys, '_force_generic_engine_')) diff --git a/testing/cffi0/snippets/setuptools_package_2/setup.py b/testing/cffi0/snippets/setuptools_package_2/setup.py new file mode 100644 index 0000000..87fb22b --- /dev/null +++ b/testing/cffi0/snippets/setuptools_package_2/setup.py @@ -0,0 +1,9 @@ + +from setuptools import setup +import snip_setuptools_verify2 + +setup( + zip_safe=False, + packages=['snip_setuptools_verify2'], + ext_package='snip_setuptools_verify2', + ext_modules=[snip_setuptools_verify2.ffi.verifier.get_extension()]) diff --git a/testing/cffi0/snippets/setuptools_package_2/snip_setuptools_verify2/__init__.py b/testing/cffi0/snippets/setuptools_package_2/snip_setuptools_verify2/__init__.py new file mode 100644 index 0000000..5f4bd13 --- /dev/null +++ b/testing/cffi0/snippets/setuptools_package_2/snip_setuptools_verify2/__init__.py @@ -0,0 +1,18 @@ + +from cffi import FFI +import sys + +ffi = FFI() +ffi.cdef(""" // some declarations from the man page + struct passwd { + char *pw_name; + ...; + }; + struct passwd *getpwuid(int uid); +""") +C = ffi.verify(""" // passed to the real C compiler +#include +#include +""", libraries=[], # or a list of libraries to link with + ext_package='snip_setuptools_verify2', + force_generic_engine=hasattr(sys, '_force_generic_engine_')) diff --git a/testing/cffi0/test_cdata.py b/testing/cffi0/test_cdata.py new file mode 100644 index 0000000..23989ab --- /dev/null +++ b/testing/cffi0/test_cdata.py @@ -0,0 +1,41 @@ +import py +from cffi import FFI + +class FakeBackend(object): + + def nonstandard_integer_types(self): + return {} + + def sizeof(self, name): + return 1 + + def load_library(self, path): + return "fake library" + + def new_primitive_type(self, name): + return FakeType("primitive " + name) + + def new_void_type(self): + return FakeType("void") + def new_pointer_type(self, x): + return FakeType('ptr-to-%r' % (x,)) + def new_array_type(self, x, y): + return FakeType('array-from-%r-len-%r' % (x, y)) + def cast(self, x, y): + return 'casted!' + def _get_types(self): + return "CData", "CType" + + buffer = "buffer type" + + +class FakeType(object): + def __init__(self, cdecl): + self.cdecl = cdecl + + +def test_typeof(): + ffi = FFI(backend=FakeBackend()) + clong = ffi.typeof("signed long int") + assert isinstance(clong, FakeType) + assert clong.cdecl == 'primitive long' diff --git a/testing/cffi0/test_ctypes.py b/testing/cffi0/test_ctypes.py new file mode 100644 index 0000000..a70c8f0 --- /dev/null +++ b/testing/cffi0/test_ctypes.py @@ -0,0 +1,43 @@ +import py, sys +from testing.cffi0 import backend_tests +from cffi.backend_ctypes import CTypesBackend + + +class TestCTypes(backend_tests.BackendTests): + # for individual tests see + # ====> backend_tests.py + + Backend = CTypesBackend + TypeRepr = "'>" + + def test_array_of_func_ptr(self): + py.test.skip("ctypes backend: not supported: " + "initializers for function pointers") + + def test_structptr_argument(self): + py.test.skip("ctypes backend: not supported: passing a list " + "for a pointer argument") + + def test_array_argument_as_list(self): + py.test.skip("ctypes backend: not supported: passing a list " + "for a pointer argument") + + def test_cast_to_array_type(self): + py.test.skip("ctypes backend: not supported: casting to array") + + def test_nested_anonymous_struct(self): + py.test.skip("ctypes backend: not supported: nested anonymous struct") + + def test_nested_field_offset_align(self): + py.test.skip("ctypes backend: not supported: nested anonymous struct") + + def test_nested_anonymous_union(self): + py.test.skip("ctypes backend: not supported: nested anonymous union") + + def test_nested_anonymous_struct_2(self): + py.test.skip("ctypes backend: not supported: nested anonymous union") + + def test_CData_CType_2(self): + if sys.version_info >= (3,): + py.test.skip("ctypes backend: not supported in Python 3: CType") + backend_tests.BackendTests.test_CData_CType_2(self) diff --git a/testing/cffi0/test_ffi_backend.py b/testing/cffi0/test_ffi_backend.py new file mode 100644 index 0000000..12ecaee --- /dev/null +++ b/testing/cffi0/test_ffi_backend.py @@ -0,0 +1,576 @@ +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]") + + +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(""" + 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("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( + "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("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( + "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() + py.test.raises(TypeError, + 'ffi.cdef("struct s1 { float x:1; };"); ffi.new("struct s1 *")') + py.test.raises(TypeError, + 'ffi.cdef("struct s2 { char x:0; };"); ffi.new("struct s2 *")') + py.test.raises(TypeError, + 'ffi.cdef("struct s3 { char x:9; };"); 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("" % (cb,) + res = fptr(b"Hello") + assert res == 42 + # + if not sys.platform.startswith('linux'): + py.test.skip("probably no symbol 'stderr' in the lib") + ffi.cdef(""" + int fputs(const char *, void *); + void *stderr; + """) + needs_dlopen_none() + ffi.C = ffi.dlopen(None) + fptr = ffi.cast("int(*)(const char *txt, void *)", ffi.C.fputs) + assert fptr == ffi.C.fputs + assert repr(fptr).startswith(" lambda (closure) -> container -> callback + return callback + + class Data(object): + pass + ffi = FFI(backend=self.Backend()) + data = Data() + callback = make_callback(data) + wr = weakref.ref(data) + del callback, data + for i in range(3): + if wr() is not None: + import gc; gc.collect() + assert wr() is None # 'data' does not leak + + def test_windows_stdcall(self): + if sys.platform != 'win32': + py.test.skip("Windows-only test") + if self.Backend is CTypesBackend: + py.test.skip("not with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency); + """) + m = ffi.dlopen("Kernel32.dll") + p_freq = ffi.new("LONGLONG *") + res = m.QueryPerformanceFrequency(p_freq) + assert res != 0 + assert p_freq[0] != 0 + + def test_explicit_cdecl_stdcall(self): + if sys.platform != 'win32': + py.test.skip("Windows-only test") + if self.Backend is CTypesBackend: + py.test.skip("not with the ctypes backend") + win64 = (sys.maxsize > 2**32) + # + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency); + """) + m = ffi.dlopen("Kernel32.dll") + tp = ffi.typeof(m.QueryPerformanceFrequency) + assert str(tp) == "" + # + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + BOOL __cdecl QueryPerformanceFrequency(LONGLONG *lpFrequency); + """) + m = ffi.dlopen("Kernel32.dll") + tpc = ffi.typeof(m.QueryPerformanceFrequency) + assert tpc is tp + # + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + BOOL WINAPI QueryPerformanceFrequency(LONGLONG *lpFrequency); + """) + m = ffi.dlopen("Kernel32.dll") + tps = ffi.typeof(m.QueryPerformanceFrequency) + if win64: + assert tps is tpc + else: + assert tps is not tpc + assert str(tps) == "" + # + ffi = FFI(backend=self.Backend()) + ffi.cdef("typedef int (__cdecl *fnc_t)(int);") + ffi.cdef("typedef int (__stdcall *fns_t)(int);") + tpc = ffi.typeof("fnc_t") + tps = ffi.typeof("fns_t") + assert str(tpc) == "" + if win64: + assert tps is tpc + else: + assert str(tps) == "" + # + fnc = ffi.cast("fnc_t", 0) + fns = ffi.cast("fns_t", 0) + ffi.new("fnc_t[]", [fnc]) + if not win64: + py.test.raises(TypeError, ffi.new, "fnc_t[]", [fns]) + py.test.raises(TypeError, ffi.new, "fns_t[]", [fnc]) + ffi.new("fns_t[]", [fns]) + + def test_stdcall_only_on_windows(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("double __stdcall sin(double x);") # stdcall ignored + m = ffi.dlopen(lib_m) + if (sys.platform == 'win32' and sys.maxsize < 2**32 and + self.Backend is not CTypesBackend): + assert "double(__stdcall *)(double)" in str(ffi.typeof(m.sin)) + else: + assert "double(*)(double)" in str(ffi.typeof(m.sin)) + x = m.sin(1.23) + assert x == math.sin(1.23) + + def test_dir_on_dlopen_lib(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef enum { MYE1, MYE2 } myenum_t; + double myfunc(double); + double myvar; + const double myconst; + #define MYFOO 42 + """) + m = ffi.dlopen(lib_m) + assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc', 'myvar'] + + def test_dlclose(self): + if self.Backend is CTypesBackend: + py.test.skip("not with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef("int foobar(void); int foobaz;") + lib = ffi.dlopen(lib_m) + ffi.dlclose(lib) + e = py.test.raises(ValueError, getattr, lib, 'foobar') + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") + e = py.test.raises(ValueError, getattr, lib, 'foobaz') + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") + e = py.test.raises(ValueError, setattr, lib, 'foobaz', 42) + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") + ffi.dlclose(lib) # does not raise diff --git a/testing/cffi0/test_model.py b/testing/cffi0/test_model.py new file mode 100644 index 0000000..bb653ca --- /dev/null +++ b/testing/cffi0/test_model.py @@ -0,0 +1,111 @@ +from cffi.model import * + + +def test_void_type(): + assert void_type.get_c_name() == "void" + assert void_type.get_c_name("foo") == "void foo" + assert void_type.get_c_name("*foo") == "void *foo" + +def test_primitive_type(): + int_type = PrimitiveType("int") + assert int_type.get_c_name() == "int" + assert int_type.get_c_name("foo") == "int foo" + assert int_type.get_c_name("*foo") == "int *foo" + assert int_type.get_c_name("[5]") == "int[5]" + +def test_raw_function_type(): + int_type = PrimitiveType("int") + fn_type = RawFunctionType([], int_type, False) + assert fn_type.get_c_name() == "int()(void)" + assert fn_type.get_c_name("*") == "int( *)(void)" + assert fn_type.get_c_name("*foo") == "int( *foo)(void)" + fn_type = RawFunctionType([int_type], int_type, False) + assert fn_type.get_c_name() == "int()(int)" + fn_type = RawFunctionType([int_type] * 2, int_type, False) + assert fn_type.get_c_name() == "int()(int, int)" + # + fn_type = RawFunctionType([int_type], int_type, True) + assert fn_type.get_c_name() == "int()(int, ...)" + assert fn_type.get_c_name("*foo") == "int( *foo)(int, ...)" + # + res_type = FunctionPtrType([int_type], int_type, True) + fn_type = RawFunctionType([int_type], res_type, True) + assert fn_type.get_c_name("x") == "int(*( x)(int, ...))(int, ...)" + +def test_function_ptr_type(): + int_type = PrimitiveType("int") + fn_type = FunctionPtrType([], int_type, False) + assert fn_type.get_c_name() == "int(*)(void)" + assert fn_type.get_c_name("*") == "int(* *)(void)" + assert fn_type.get_c_name("*foo") == "int(* *foo)(void)" + fn_type = FunctionPtrType([int_type], int_type, False) + assert fn_type.get_c_name() == "int(*)(int)" + fn_type = FunctionPtrType([int_type] * 2, int_type, False) + assert fn_type.get_c_name() == "int(*)(int, int)" + # + fn_type = FunctionPtrType([int_type], int_type, True) + assert fn_type.get_c_name() == "int(*)(int, ...)" + +def test_pointer_type(): + ptr_type = PointerType(PrimitiveType("int")) + assert ptr_type.get_c_name("x") == "int * x" + +def test_const_pointer_type(): + ptr_type = ConstPointerType(PrimitiveType("int")) + assert ptr_type.get_c_name("x") == "int const * x" + ptr_type = ConstPointerType(ArrayType(PrimitiveType("int"), 5)) + assert ptr_type.get_c_name("") == "int(const *)[5]" + assert ptr_type.get_c_name("*x") == "int(const * *x)[5]" + +def test_qual_pointer_type(): + ptr_type = PointerType(PrimitiveType("long long"), Q_RESTRICT) + assert ptr_type.get_c_name("") == "long long __restrict *" + assert const_voidp_type.get_c_name("") == "void const *" + +def test_unknown_pointer_type(): + ptr_type = unknown_ptr_type("foo_p") + assert ptr_type.get_c_name("") == "foo_p" + assert ptr_type.get_c_name("x") == "foo_p x" + +def test_unknown_type(): + u_type = unknown_type("foo_t") + assert u_type.get_c_name("") == "foo_t" + assert u_type.get_c_name("x") == "foo_t x" + +def test_array_type(): + a_type = ArrayType(PrimitiveType("int"), None) + assert a_type.get_c_name("") == "int[]" + assert a_type.get_c_name("x") == "int x[]" + assert a_type.get_c_name("*x") == "int(*x)[]" + assert a_type.get_c_name(" *x") == "int(*x)[]" + assert a_type.get_c_name("[5]") == "int[5][]" + a_type = ArrayType(unknown_type("foo_t"), 5) + assert a_type.get_c_name("") == "foo_t[5]" + assert a_type.get_c_name("x") == "foo_t x[5]" + assert a_type.get_c_name("*x") == "foo_t(*x)[5]" + a_type = ArrayType(unknown_ptr_type("foo_p"), None) + assert a_type.get_c_name("") == "foo_p[]" + assert a_type.get_c_name("x") == "foo_p x[]" + assert a_type.get_c_name("*x") == "foo_p(*x)[]" + a_type = ArrayType(ConstPointerType(PrimitiveType("int")), None) + assert a_type.get_c_name("") == "int const *[]" + assert a_type.get_c_name("x") == "int const * x[]" + assert a_type.get_c_name("*x") == "int const *(*x)[]" + fn_type = FunctionPtrType([], PrimitiveType("int"), False) + a_type = ArrayType(fn_type, 5) + assert a_type.get_c_name("") == "int(*[5])(void)" + assert a_type.get_c_name("x") == "int(* x[5])(void)" + assert a_type.get_c_name("*x") == "int(*(*x)[5])(void)" + +def test_struct_type(): + struct_type = StructType("foo_s", None, None, None) + assert struct_type.get_c_name() == "struct foo_s" + assert struct_type.get_c_name("*x") == "struct foo_s *x" + +def test_union_type(): + union_type = UnionType("foo_s", None, None, None) + assert union_type.get_c_name() == "union foo_s" + +def test_enum_type(): + enum_type = EnumType("foo_e", [], []) + assert enum_type.get_c_name() == "enum foo_e" diff --git a/testing/cffi0/test_ownlib.py b/testing/cffi0/test_ownlib.py new file mode 100644 index 0000000..a06df20 --- /dev/null +++ b/testing/cffi0/test_ownlib.py @@ -0,0 +1,373 @@ +import py, sys, os +import subprocess, weakref +from cffi import FFI +from cffi.backend_ctypes import CTypesBackend +from testing.support import u + + +SOURCE = """\ +#include + +#ifdef _WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +EXPORT int test_getting_errno(void) { + errno = 123; + return -1; +} + +EXPORT int test_setting_errno(void) { + return errno; +}; + +typedef struct { + long x; + long y; +} POINT; + +typedef struct { + long left; + long top; + long right; + long bottom; +} RECT; + + +EXPORT int PointInRect(RECT *prc, POINT pt) +{ + if (pt.x < prc->left) + return 0; + if (pt.x > prc->right) + return 0; + if (pt.y < prc->top) + return 0; + if (pt.y > prc->bottom) + return 0; + return 1; +}; + +EXPORT long left = 10; +EXPORT long top = 20; +EXPORT long right = 30; +EXPORT long bottom = 40; + +EXPORT RECT ReturnRect(int i, RECT ar, RECT* br, POINT cp, RECT dr, + RECT *er, POINT fp, RECT gr) +{ + /*Check input */ + if (ar.left + br->left + dr.left + er->left + gr.left != left * 5) + { + ar.left = 100; + return ar; + } + if (ar.right + br->right + dr.right + er->right + gr.right != right * 5) + { + ar.right = 100; + return ar; + } + if (cp.x != fp.x) + { + ar.left = -100; + } + if (cp.y != fp.y) + { + ar.left = -200; + } + switch(i) + { + case 0: + return ar; + break; + case 1: + return dr; + break; + case 2: + return gr; + break; + + } + return ar; +} + +EXPORT int my_array[7] = {0, 1, 2, 3, 4, 5, 6}; + +EXPORT unsigned short foo_2bytes(unsigned short a) +{ + return (unsigned short)(a + 42); +} +EXPORT unsigned int foo_4bytes(unsigned int a) +{ + return (unsigned int)(a + 42); +} + +EXPORT void modify_struct_value(RECT r) +{ + r.left = r.right = r.top = r.bottom = 500; +} +""" + +class TestOwnLib(object): + Backend = CTypesBackend + + def setup_class(cls): + cls.module = None + from testing.udir import udir + udir.join('testownlib.c').write(SOURCE) + if sys.platform == 'win32': + # did we already build it? + if cls.Backend is CTypesBackend: + dll_path = str(udir) + '\\testownlib1.dll' # only ascii for the ctypes backend + else: + dll_path = str(udir) + '\\' + (u+'testownlib\u03be.dll') # non-ascii char + if os.path.exists(dll_path): + cls.module = dll_path + return + # try (not too hard) to find the version used to compile this python + # no mingw + from distutils.msvc9compiler import get_build_version + version = get_build_version() + toolskey = "VS%0.f0COMNTOOLS" % version + toolsdir = os.environ.get(toolskey, None) + if toolsdir is None: + return + productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") + productdir = os.path.abspath(productdir) + vcvarsall = os.path.join(productdir, "vcvarsall.bat") + # 64? + arch = 'x86' + if sys.maxsize > 2**32: + arch = 'amd64' + if os.path.isfile(vcvarsall): + cmd = '"%s" %s' % (vcvarsall, arch) + ' & cl.exe testownlib.c ' \ + ' /LD /Fetestownlib.dll' + subprocess.check_call(cmd, cwd = str(udir), shell=True) + os.rename(str(udir) + '\\testownlib.dll', dll_path) + cls.module = dll_path + else: + encoded = None + if cls.Backend is not CTypesBackend: + try: + unicode_name = u+'testownlibcaf\xe9' + encoded = unicode_name.encode(sys.getfilesystemencoding()) + if sys.version_info >= (3,): + encoded = str(unicode_name) + except UnicodeEncodeError: + pass + if encoded is None: + unicode_name = u+'testownlib' + encoded = str(unicode_name) + subprocess.check_call( + "cc testownlib.c -shared -fPIC -o '%s.so'" % (encoded,), + cwd=str(udir), shell=True) + cls.module = os.path.join(str(udir), unicode_name + (u+'.so')) + print(repr(cls.module)) + + def test_getting_errno(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if sys.platform == 'win32': + py.test.skip("fails, errno at multiple addresses") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int test_getting_errno(void); + """) + ownlib = ffi.dlopen(self.module) + res = ownlib.test_getting_errno() + assert res == -1 + assert ffi.errno == 123 + + def test_setting_errno(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if sys.platform == 'win32': + py.test.skip("fails, errno at multiple addresses") + if self.Backend is CTypesBackend and '__pypy__' in sys.modules: + py.test.skip("XXX errno issue with ctypes on pypy?") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int test_setting_errno(void); + """) + ownlib = ffi.dlopen(self.module) + ffi.errno = 42 + res = ownlib.test_setting_errno() + assert res == 42 + assert ffi.errno == 42 + + def test_my_array_7(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int my_array[7]; + """) + ownlib = ffi.dlopen(self.module) + for i in range(7): + assert ownlib.my_array[i] == i + assert len(ownlib.my_array) == 7 + if self.Backend is CTypesBackend: + py.test.skip("not supported by the ctypes backend") + ownlib.my_array = list(range(10, 17)) + for i in range(7): + assert ownlib.my_array[i] == 10 + i + ownlib.my_array = list(range(7)) + for i in range(7): + assert ownlib.my_array[i] == i + + def test_my_array_no_length(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if self.Backend is CTypesBackend: + py.test.skip("not supported by the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int my_array[]; + """) + ownlib = ffi.dlopen(self.module) + for i in range(7): + assert ownlib.my_array[i] == i + py.test.raises(TypeError, len, ownlib.my_array) + ownlib.my_array = list(range(10, 17)) + for i in range(7): + assert ownlib.my_array[i] == 10 + i + ownlib.my_array = list(range(7)) + for i in range(7): + assert ownlib.my_array[i] == i + + def test_keepalive_lib(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int test_getting_errno(void); + """) + ownlib = ffi.dlopen(self.module) + ffi_r = weakref.ref(ffi) + ownlib_r = weakref.ref(ownlib) + func = ownlib.test_getting_errno + del ffi + import gc; gc.collect() # ownlib stays alive + assert ownlib_r() is not None + assert ffi_r() is not None # kept alive by ownlib + res = func() + assert res == -1 + + def test_keepalive_ffi(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int test_getting_errno(void); + """) + ownlib = ffi.dlopen(self.module) + ffi_r = weakref.ref(ffi) + ownlib_r = weakref.ref(ownlib) + func = ownlib.test_getting_errno + del ownlib + import gc; gc.collect() # ffi stays alive + assert ffi_r() is not None + assert ownlib_r() is not None # kept alive by ffi + res = func() + assert res == -1 + if sys.platform != 'win32': # else, errno at multiple addresses + assert ffi.errno == 123 + + def test_struct_by_value(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef struct { + long x; + long y; + } POINT; + + typedef struct { + long left; + long top; + long right; + long bottom; + } RECT; + + long left, top, right, bottom; + + RECT ReturnRect(int i, RECT ar, RECT* br, POINT cp, RECT dr, + RECT *er, POINT fp, RECT gr); + """) + ownlib = ffi.dlopen(self.module) + + rect = ffi.new('RECT[1]') + pt = ffi.new('POINT[1]') + pt[0].x = 15 + pt[0].y = 25 + rect[0].left = ownlib.left + rect[0].right = ownlib.right + rect[0].top = ownlib.top + rect[0].bottom = ownlib.bottom + + for i in range(4): + ret = ownlib.ReturnRect(i, rect[0], rect, pt[0], rect[0], + rect, pt[0], rect[0]) + assert ret.left == ownlib.left + assert ret.right == ownlib.right + assert ret.top == ownlib.top + assert ret.bottom == ownlib.bottom + + def test_addressof_lib(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if self.Backend is CTypesBackend: + py.test.skip("not implemented with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef("long left; int test_getting_errno(void);") + lib = ffi.dlopen(self.module) + lib.left = 123456 + p = ffi.addressof(lib, "left") + assert ffi.typeof(p) == ffi.typeof("long *") + assert p[0] == 123456 + p[0] += 1 + assert lib.left == 123457 + pfn = ffi.addressof(lib, "test_getting_errno") + assert ffi.typeof(pfn) == ffi.typeof("int(*)(void)") + assert pfn == lib.test_getting_errno + + def test_char16_char32_t(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if self.Backend is CTypesBackend: + py.test.skip("not implemented with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + char16_t foo_2bytes(char16_t); + char32_t foo_4bytes(char32_t); + """) + lib = ffi.dlopen(self.module) + assert lib.foo_2bytes(u+'\u1234') == u+'\u125e' + assert lib.foo_4bytes(u+'\u1234') == u+'\u125e' + assert lib.foo_4bytes(u+'\U00012345') == u+'\U0001236f' + + def test_modify_struct_value(self): + if self.module is None: + py.test.skip("fix the auto-generation of the tiny test lib") + if self.Backend is CTypesBackend: + py.test.skip("fails with the ctypes backend on some architectures") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef struct { + long left; + long top; + long right; + long bottom; + } RECT; + + void modify_struct_value(RECT r); + """) + lib = ffi.dlopen(self.module) + s = ffi.new("RECT *", [11, 22, 33, 44]) + lib.modify_struct_value(s[0]) + assert s.left == 11 + assert s.top == 22 + assert s.right == 33 + assert s.bottom == 44 diff --git a/testing/cffi0/test_parsing.py b/testing/cffi0/test_parsing.py new file mode 100644 index 0000000..2d75850 --- /dev/null +++ b/testing/cffi0/test_parsing.py @@ -0,0 +1,468 @@ +import py, sys, re +from cffi import FFI, FFIError, CDefError, VerificationError +from .backend_tests import needs_dlopen_none + + +class FakeBackend(object): + + def nonstandard_integer_types(self): + return {} + + def sizeof(self, name): + return 1 + + def load_library(self, name, flags): + if sys.platform == 'win32': + assert name is None or "msvcr" in name + else: + assert name is None or "libc" in name or "libm" in name + return FakeLibrary() + + def new_function_type(self, args, result, has_varargs): + args = [arg.cdecl for arg in args] + result = result.cdecl + return FakeType( + '' % (', '.join(args), result, has_varargs)) + + def new_primitive_type(self, name): + assert name == name.lower() + return FakeType('<%s>' % name) + + def new_pointer_type(self, itemtype): + return FakeType('' % (itemtype,)) + + def new_struct_type(self, name): + return FakeStruct(name) + + def complete_struct_or_union(self, s, fields, tp=None, + totalsize=-1, totalalignment=-1, sflags=0): + assert isinstance(s, FakeStruct) + s.fields = fields + + def new_array_type(self, ptrtype, length): + return FakeType('' % (ptrtype, length)) + + def new_void_type(self): + return FakeType("") + def cast(self, x, y): + return 'casted!' + def _get_types(self): + return "CData", "CType" + + buffer = "buffer type" + +class FakeType(object): + def __init__(self, cdecl): + self.cdecl = cdecl + def __str__(self): + return self.cdecl + +class FakeStruct(object): + def __init__(self, name): + self.name = name + def __str__(self): + return ', '.join([str(y) + str(x) for x, y, z in self.fields]) + +class FakeLibrary(object): + + def load_function(self, BType, name): + return FakeFunction(BType, name) + +class FakeFunction(object): + + def __init__(self, BType, name): + self.BType = str(BType) + self.name = name + +lib_m = "m" +if sys.platform == 'win32': + #there is a small chance this fails on Mingw via environ $CC + import distutils.ccompiler + if distutils.ccompiler.get_default_compiler() == 'msvc': + lib_m = 'msvcrt' + +def test_simple(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef("double sin(double x);") + m = ffi.dlopen(lib_m) + func = m.sin # should be a callable on real backends + assert func.name == 'sin' + assert func.BType == '), , False>' + +def test_pipe(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef("int pipe(int pipefd[2]);") + needs_dlopen_none() + C = ffi.dlopen(None) + func = C.pipe + assert func.name == 'pipe' + assert func.BType == '>), , False>' + +def test_vararg(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef("short foo(int, ...);") + needs_dlopen_none() + C = ffi.dlopen(None) + func = C.foo + assert func.name == 'foo' + assert func.BType == '), , True>' + +def test_no_args(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + int foo(void); + """) + needs_dlopen_none() + C = ffi.dlopen(None) + assert C.foo.BType == ', False>' + +def test_typedef(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + typedef unsigned int UInt; + typedef UInt UIntReally; + UInt foo(void); + """) + needs_dlopen_none() + C = ffi.dlopen(None) + assert str(ffi.typeof("UIntReally")) == '' + assert C.foo.BType == ', False>' + +def test_typedef_more_complex(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + typedef struct { int a, b; } foo_t, *foo_p; + int foo(foo_p[]); + """) + needs_dlopen_none() + C = ffi.dlopen(None) + assert str(ffi.typeof("foo_t")) == 'a, b' + assert str(ffi.typeof("foo_p")) == 'a, b>' + assert C.foo.BType == ('a, b>>), , False>') + +def test_typedef_array_convert_array_to_pointer(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + typedef int (*fn_t)(int[5]); + """) + with ffi._lock: + type = ffi._parser.parse_type("fn_t") + BType = ffi._get_cached_btype(type) + assert str(BType) == '>), , False>' + +def test_remove_comments(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + double /*comment here*/ sin // blah blah + /* multi- + line- + //comment */ ( + // foo + double // bar /* <- ignored, because it's in a comment itself + x, double/*several*//*comment*/y) /*on the same line*/ + ; + """) + m = ffi.dlopen(lib_m) + func = m.sin + assert func.name == 'sin' + assert func.BType == ', ), , False>' + +def test_remove_line_continuation_comments(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + double // blah \\ + more comments + x(void); + double // blah\\\\ + y(void); + double // blah\\ \ + etc + z(void); + """) + m = ffi.dlopen(lib_m) + m.x + m.y + m.z + +def test_line_continuation_in_defines(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef(""" + #define ABC\\ + 42 + #define BCD \\ + 43 + """) + m = ffi.dlopen(lib_m) + assert m.ABC == 42 + assert m.BCD == 43 + +def test_define_not_supported_for_now(): + ffi = FFI(backend=FakeBackend()) + e = py.test.raises(CDefError, ffi.cdef, '#define FOO "blah"') + assert str(e.value) == ( + 'only supports one of the following syntax:\n' + ' #define FOO ... (literally dot-dot-dot)\n' + ' #define FOO NUMBER (with NUMBER an integer' + ' constant, decimal/hex/octal)\n' + 'got:\n' + ' #define FOO "blah"') + +def test_unnamed_struct(): + ffi = FFI(backend=FakeBackend()) + ffi.cdef("typedef struct { int x; } foo_t;\n" + "typedef struct { int y; } *bar_p;\n") + assert 'typedef foo_t' in ffi._parser._declarations + assert 'typedef bar_p' in ffi._parser._declarations + assert 'anonymous foo_t' in ffi._parser._declarations + type_foo = ffi._parser.parse_type("foo_t") + type_bar = ffi._parser.parse_type("bar_p").totype + assert repr(type_foo) == "" + assert repr(type_bar) == "" + py.test.raises(VerificationError, type_bar.get_c_name) + assert type_foo.get_c_name() == "foo_t" + +def test_override(): + ffi = FFI(backend=FakeBackend()) + needs_dlopen_none() + C = ffi.dlopen(None) + ffi.cdef("int foo(void);") + py.test.raises(FFIError, ffi.cdef, "long foo(void);") + assert C.foo.BType == ', False>' + ffi.cdef("long foo(void);", override=True) + assert C.foo.BType == ', False>' + +def test_cannot_have_only_variadic_part(): + # this checks that we get a sensible error if we try "int foo(...);" + ffi = FFI() + e = py.test.raises(CDefError, ffi.cdef, "int foo(...);") + assert str(e.value) == ( + ":1: foo: a function with only '(...)' " + "as argument is not correct C") + +def test_parse_error(): + ffi = FFI() + e = py.test.raises(CDefError, ffi.cdef, " x y z ") + assert str(e.value).startswith( + 'cannot parse "x y z"\n:1:') + e = py.test.raises(CDefError, ffi.cdef, "\n\n\n x y z ") + assert str(e.value).startswith( + 'cannot parse "x y z"\n:4:') + +def test_error_custom_lineno(): + ffi = FFI() + e = py.test.raises(CDefError, ffi.cdef, """ +# 42 "foobar" + + a b c d + """) + assert str(e.value).startswith('parse error\nfoobar:43:') + +def test_cannot_declare_enum_later(): + ffi = FFI() + e = py.test.raises(NotImplementedError, ffi.cdef, + "typedef enum foo_e foo_t; enum foo_e { AA, BB };") + assert str(e.value) == ( + "enum foo_e: the '{}' declaration should appear on the " + "first time the enum is mentioned, not later") + +def test_unknown_name(): + ffi = FFI() + e = py.test.raises(CDefError, ffi.cast, "foobarbazunknown", 0) + assert str(e.value) == "unknown identifier 'foobarbazunknown'" + e = py.test.raises(CDefError, ffi.cast, "foobarbazunknown*", 0) + assert str(e.value).startswith('cannot parse "foobarbazunknown*"') + e = py.test.raises(CDefError, ffi.cast, "int(*)(foobarbazunknown)", 0) + assert str(e.value).startswith('cannot parse "int(*)(foobarbazunknown)"') + +def test_redefine_common_type(): + prefix = "" if sys.version_info < (3,) else "b" + ffi = FFI() + ffi.cdef("typedef char FILE;") + assert repr(ffi.cast("FILE", 123)) == "" % prefix + ffi.cdef("typedef char int32_t;") + assert repr(ffi.cast("int32_t", 123)) == "" % prefix + ffi = FFI() + ffi.cdef("typedef int bool, *FILE;") + assert repr(ffi.cast("bool", 123)) == "" + assert re.match(r"", + repr(ffi.cast("FILE", 123))) + ffi = FFI() + ffi.cdef("typedef bool (*fn_t)(bool, bool);") # "bool," but within "( )" + +def test_bool(): + ffi = FFI() + ffi.cdef("void f(bool);") + # + ffi = FFI() + ffi.cdef("typedef _Bool bool; void f(bool);") + +def test_unknown_argument_type(): + ffi = FFI() + e = py.test.raises(CDefError, ffi.cdef, "void f(foobarbazzz);") + assert str(e.value) == (":1: f arg 1:" + " unknown type 'foobarbazzz' (if you meant" + " to use the old C syntax of giving untyped" + " arguments, it is not supported)") + +def test_void_renamed_as_only_arg(): + ffi = FFI() + ffi.cdef("typedef void void_t1;" + "typedef void_t1 void_t;" + "typedef int (*func_t)(void_t);") + assert ffi.typeof("func_t").args == () + +def test_WPARAM_on_windows(): + if sys.platform != 'win32': + py.test.skip("Only for Windows") + ffi = FFI() + ffi.cdef("void f(WPARAM);") + # + # WPARAM -> UINT_PTR -> unsigned 32/64-bit integer + ffi = FFI() + value = int(ffi.cast("WPARAM", -42)) + assert value == sys.maxsize * 2 - 40 + +def test__is_constant_globalvar(): + for input, expected_output in [ + ("int a;", False), + ("const int a;", True), + ("int *a;", False), + ("const int *a;", False), + ("int const *a;", False), + ("int *const a;", True), + ("int a[5];", False), + ("const int a[5];", False), + ("int *a[5];", False), + ("const int *a[5];", False), + ("int const *a[5];", False), + ("int *const a[5];", False), + ("int a[5][6];", False), + ("const int a[5][6];", False), + ]: + ffi = FFI() + ffi.cdef(input) + declarations = ffi._parser._declarations + assert ('constant a' in declarations) == expected_output + assert ('variable a' in declarations) == (not expected_output) + +def test_restrict(): + from cffi import model + for input, expected_output in [ + ("int a;", False), + ("restrict int a;", True), + ("int *a;", False), + ]: + ffi = FFI() + ffi.cdef(input) + tp, quals = ffi._parser._declarations['variable a'] + assert bool(quals & model.Q_RESTRICT) == expected_output + +def test_different_const_funcptr_types(): + lst = [] + for input in [ + "int(*)(int *a)", + "int(*)(int const *a)", + "int(*)(int * const a)", + "int(*)(int const a[])"]: + ffi = FFI(backend=FakeBackend()) + lst.append(ffi._parser.parse_type(input)) + assert lst[0] != lst[1] + assert lst[0] == lst[2] + assert lst[1] == lst[3] + +def test_const_pointer_to_pointer(): + from cffi import model + ffi = FFI(backend=FakeBackend()) + # + tp, qual = ffi._parser.parse_type_and_quals("char * * (* const)") + assert (str(tp), qual) == ("", model.Q_CONST) + tp, qual = ffi._parser.parse_type_and_quals("char * (* const (*))") + assert (str(tp), qual) == ("", 0) + tp, qual = ffi._parser.parse_type_and_quals("char (* const (* (*)))") + assert (str(tp), qual) == ("", 0) + tp, qual = ffi._parser.parse_type_and_quals("char const * * *") + assert (str(tp), qual) == ("", 0) + tp, qual = ffi._parser.parse_type_and_quals("const char * * *") + assert (str(tp), qual) == ("", 0) + # + tp, qual = ffi._parser.parse_type_and_quals("char * * * const const") + assert (str(tp), qual) == ("", model.Q_CONST) + tp, qual = ffi._parser.parse_type_and_quals("char * * volatile *") + assert (str(tp), qual) == ("", 0) + tp, qual = ffi._parser.parse_type_and_quals("char * volatile restrict * *") + assert (str(tp), qual) == ("", 0) + tp, qual = ffi._parser.parse_type_and_quals("char const volatile * * *") + assert (str(tp), qual) == ("", 0) + tp, qual = ffi._parser.parse_type_and_quals("const char * * *") + assert (str(tp), qual) == ("", 0) + # + tp, qual = ffi._parser.parse_type_and_quals( + "int(char*const*, short****const*)") + assert (str(tp), qual) == ( + "", 0) + tp, qual = ffi._parser.parse_type_and_quals( + "char*const*(short*const****)") + assert (str(tp), qual) == ( + "", 0) + +def test_enum(): + ffi = FFI() + ffi.cdef(""" + enum Enum { POS = +1, TWO = 2, NIL = 0, NEG = -1, OP = (POS+TWO)-1}; + """) + needs_dlopen_none() + C = ffi.dlopen(None) + assert C.POS == 1 + assert C.TWO == 2 + assert C.NIL == 0 + assert C.NEG == -1 + assert C.OP == 2 + +def test_stdcall(): + ffi = FFI() + tp = ffi.typeof("int(*)(int __stdcall x(int)," + " long (__cdecl*y)(void)," + " short(WINAPI *z)(short))") + if sys.platform == 'win32' and sys.maxsize < 2**32: + stdcall = '__stdcall ' + else: + stdcall = '' + assert str(tp) == ( + "" % (stdcall, stdcall)) + +def test_extern_python(): + ffi = FFI() + ffi.cdef(""" + int bok(int, int); + extern "Python" int foobar(int, int); + int baz(int, int); + """) + assert sorted(ffi._parser._declarations) == [ + 'extern_python foobar', 'function baz', 'function bok'] + assert (ffi._parser._declarations['function bok'] == + ffi._parser._declarations['extern_python foobar'] == + ffi._parser._declarations['function baz']) + +def test_extern_python_group(): + ffi = FFI() + ffi.cdef(""" + int bok(int); + extern "Python" {int foobar(int, int);int bzrrr(int);} + int baz(int, int); + """) + assert sorted(ffi._parser._declarations) == [ + 'extern_python bzrrr', 'extern_python foobar', + 'function baz', 'function bok'] + assert (ffi._parser._declarations['function baz'] == + ffi._parser._declarations['extern_python foobar'] != + ffi._parser._declarations['function bok'] == + ffi._parser._declarations['extern_python bzrrr']) + +def test_error_invalid_syntax_for_cdef(): + ffi = FFI() + e = py.test.raises(CDefError, ffi.cdef, 'void foo(void) {}') + assert str(e.value) == (':1: unexpected : ' + 'this construct is valid C but not valid in cdef()') diff --git a/testing/cffi0/test_platform.py b/testing/cffi0/test_platform.py new file mode 100644 index 0000000..55446ec --- /dev/null +++ b/testing/cffi0/test_platform.py @@ -0,0 +1,25 @@ +import os +from cffi.ffiplatform import maybe_relative_path, flatten + + +def test_not_absolute(): + assert maybe_relative_path('foo/bar') == 'foo/bar' + assert maybe_relative_path('test_platform.py') == 'test_platform.py' + +def test_different_absolute(): + p = os.path.join('..', 'baz.py') + assert maybe_relative_path(p) == p + +def test_absolute_mapping(): + p = os.path.abspath('baz.py') + assert maybe_relative_path(p) == 'baz.py' + foobaz = os.path.join('foo', 'baz.py') + assert maybe_relative_path(os.path.abspath(foobaz)) == foobaz + +def test_flatten(): + assert flatten("foo") == "3sfoo" + assert flatten(-10000000000000000000000000000) == \ + "-10000000000000000000000000000i" + assert flatten([4, 5]) == "2l4i5i" + assert flatten({4: 5}) == "1d4i5i" + assert flatten({"foo": ("bar", "baaz")}) == "1d3sfoo2l3sbar4sbaaz" diff --git a/testing/cffi0/test_unicode_literals.py b/testing/cffi0/test_unicode_literals.py new file mode 100644 index 0000000..7b0a5cc --- /dev/null +++ b/testing/cffi0/test_unicode_literals.py @@ -0,0 +1,79 @@ +# +# ---------------------------------------------- +# WARNING, ALL LITERALS IN THIS FILE ARE UNICODE +# ---------------------------------------------- +# +from __future__ import unicode_literals +# +# +# +import sys, math +from cffi import FFI + +lib_m = "m" +if sys.platform == 'win32': + #there is a small chance this fails on Mingw via environ $CC + import distutils.ccompiler + if distutils.ccompiler.get_default_compiler() == 'msvc': + lib_m = 'msvcrt' + + +def test_cast(): + ffi = FFI() + assert int(ffi.cast("int", 3.14)) == 3 # unicode literal + +def test_new(): + ffi = FFI() + assert ffi.new("int[]", [3, 4, 5])[2] == 5 # unicode literal + +def test_typeof(): + ffi = FFI() + tp = ffi.typeof("int[51]") # unicode literal + assert tp.length == 51 + +def test_sizeof(): + ffi = FFI() + assert ffi.sizeof("int[51]") == 51 * 4 # unicode literal + +def test_alignof(): + ffi = FFI() + assert ffi.alignof("int[51]") == 4 # unicode literal + +def test_getctype(): + ffi = FFI() + assert ffi.getctype("int**") == "int * *" # unicode literal + assert type(ffi.getctype("int**")) is str + +def test_cdef(): + ffi = FFI() + ffi.cdef("typedef int foo_t[50];") # unicode literal + +def test_offsetof(): + ffi = FFI() + ffi.cdef("typedef struct { int x, y; } foo_t;") + assert ffi.offsetof("foo_t", "y") == 4 # unicode literal + +def test_enum(): + ffi = FFI() + ffi.cdef("enum foo_e { AA, BB, CC };") # unicode literal + x = ffi.cast("enum foo_e", 1) + assert int(ffi.cast("int", x)) == 1 + +def test_dlopen(): + ffi = FFI() + ffi.cdef("double sin(double x);") + m = ffi.dlopen(lib_m) # unicode literal + x = m.sin(1.23) + assert x == math.sin(1.23) + +def test_verify(): + ffi = FFI() + ffi.cdef("double test_verify_1(double x);") # unicode literal + lib = ffi.verify("double test_verify_1(double x) { return x * 42.0; }") + assert lib.test_verify_1(-1.5) == -63.0 + +def test_callback(): + ffi = FFI() + cb = ffi.callback("int(int)", # unicode literal + lambda x: x + 42) + assert cb(5) == 47 diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py new file mode 100644 index 0000000..79e1c6c --- /dev/null +++ b/testing/cffi0/test_verify.py @@ -0,0 +1,2524 @@ +import py, re +import sys, os, math, weakref +from cffi import FFI, VerificationError, VerificationMissing, model, FFIError +from testing.support import * + + +lib_m = ['m'] +if sys.platform == 'win32': + #there is a small chance this fails on Mingw via environ $CC + import distutils.ccompiler + if distutils.ccompiler.get_default_compiler() == 'msvc': + lib_m = ['msvcrt'] + pass # no obvious -Werror equivalent on MSVC +else: + if (sys.platform == 'darwin' and + [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]): + # assume a standard clang or gcc + extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] + # special things for clang + extra_compile_args.append('-Qunused-arguments') + else: + # assume a standard gcc + extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] + + class FFI(FFI): + def verify(self, *args, **kwds): + return super(FFI, self).verify( + *args, extra_compile_args=extra_compile_args, **kwds) + +def setup_module(): + import cffi.verifier + cffi.verifier.cleanup_tmpdir() + # + # check that no $ sign is produced in the C file; it used to be the + # case that anonymous enums would produce '$enum_$1', which was + # used as part of a function name. GCC accepts such names, but it's + # apparently non-standard. + _r_comment = re.compile(r"/\*.*?\*/|//.*?$", re.DOTALL | re.MULTILINE) + _r_string = re.compile(r'\".*?\"') + def _write_source_and_check(self, file=None): + base_write_source(self, file) + if file is None: + f = open(self.sourcefilename) + data = f.read() + f.close() + data = _r_comment.sub(' ', data) + data = _r_string.sub('"skipped"', data) + assert '$' not in data + base_write_source = cffi.verifier.Verifier._write_source + cffi.verifier.Verifier._write_source = _write_source_and_check + + +def test_module_type(): + import cffi.verifier + ffi = FFI() + lib = ffi.verify() + if hasattr(lib, '_cffi_python_module'): + print('verify got a PYTHON module') + if hasattr(lib, '_cffi_generic_module'): + print('verify got a GENERIC module') + expected_generic = (cffi.verifier._FORCE_GENERIC_ENGINE or + '__pypy__' in sys.builtin_module_names) + assert hasattr(lib, '_cffi_python_module') == (not expected_generic) + assert hasattr(lib, '_cffi_generic_module') == expected_generic + +def test_missing_function(ffi=None): + # uses the FFI hacked above with '-Werror' + if ffi is None: + ffi = FFI() + ffi.cdef("void some_completely_unknown_function();") + try: + lib = ffi.verify() + except (VerificationError, OSError): + pass # expected case: we get a VerificationError + else: + # but depending on compiler and loader details, maybe + # 'lib' could actually be imported but will fail if we + # actually try to call the unknown function... Hard + # to test anything more. + pass + +def test_missing_function_import_error(): + # uses the original FFI that just gives a warning during compilation + import cffi + test_missing_function(ffi=cffi.FFI()) + +def test_simple_case(): + ffi = FFI() + ffi.cdef("double sin(double x);") + lib = ffi.verify('#include ', libraries=lib_m) + assert lib.sin(1.23) == math.sin(1.23) + +def _Wconversion(cdef, source, **kargs): + if sys.platform in ('win32', 'darwin'): + py.test.skip("needs GCC") + ffi = FFI() + ffi.cdef(cdef) + py.test.raises(VerificationError, ffi.verify, source, **kargs) + extra_compile_args_orig = extra_compile_args[:] + extra_compile_args.remove('-Wconversion') + try: + lib = ffi.verify(source, **kargs) + finally: + extra_compile_args[:] = extra_compile_args_orig + return lib + +def test_Wconversion_unsigned(): + _Wconversion("unsigned foo(void);", + "int foo(void) { return -1;}") + +def test_Wconversion_integer(): + _Wconversion("short foo(void);", + "long long foo(void) { return 1<", libraries=lib_m) + res = lib.sin(1.23) + assert res != math.sin(1.23) # not exact, because of double->float + assert abs(res - math.sin(1.23)) < 1E-5 + +def test_Wconversion_float2int(): + _Wconversion("int sinf(float);", + "#include ", libraries=lib_m) + +def test_Wconversion_double2int(): + _Wconversion("int sin(double);", + "#include ", libraries=lib_m) + +def test_rounding_1(): + ffi = FFI() + ffi.cdef("double sinf(float x);") + lib = ffi.verify('#include ', libraries=lib_m) + res = lib.sinf(1.23) + assert res != math.sin(1.23) # not exact, because of double->float + assert abs(res - math.sin(1.23)) < 1E-5 + +def test_rounding_2(): + ffi = FFI() + ffi.cdef("double sin(float x);") + lib = ffi.verify('#include ', libraries=lib_m) + res = lib.sin(1.23) + assert res != math.sin(1.23) # not exact, because of double->float + assert abs(res - math.sin(1.23)) < 1E-5 + +def test_strlen_exact(): + ffi = FFI() + ffi.cdef("size_t strlen(const char *s);") + lib = ffi.verify("#include ") + assert lib.strlen(b"hi there!") == 9 + +def test_strlen_approximate(): + lib = _Wconversion("int strlen(char *s);", + "#include ") + assert lib.strlen(b"hi there!") == 9 + +def test_return_approximate(): + for typename in ['short', 'int', 'long', 'long long']: + ffi = FFI() + ffi.cdef("%s foo(signed char x);" % typename) + lib = ffi.verify("signed char foo(signed char x) { return x;}") + assert lib.foo(-128) == -128 + assert lib.foo(+127) == +127 + +def test_strlen_array_of_char(): + ffi = FFI() + ffi.cdef("size_t strlen(char[]);") + lib = ffi.verify("#include ") + assert lib.strlen(b"hello") == 5 + +def test_longdouble(): + ffi = FFI() + ffi.cdef("long double sinl(long double x);") + lib = ffi.verify('#include ', libraries=lib_m) + for input in [1.23, + ffi.cast("double", 1.23), + ffi.cast("long double", 1.23)]: + x = lib.sinl(input) + assert repr(x).startswith(" 0.1 + # Check the particular results on Intel + import platform + if (platform.machine().startswith('i386') or + platform.machine().startswith('i486') or + platform.machine().startswith('i586') or + platform.machine().startswith('i686') or + platform.machine().startswith('x86')): + assert abs(more_precise - 0.656769) < 0.001 + assert abs(less_precise - 3.99091) < 0.001 + else: + py.test.skip("don't know the very exact precision of 'long double'") + + +all_primitive_types = model.PrimitiveType.ALL_PRIMITIVE_TYPES +if sys.platform == 'win32': + all_primitive_types = all_primitive_types.copy() + del all_primitive_types['ssize_t'] +all_integer_types = sorted(tp for tp in all_primitive_types + if all_primitive_types[tp] == 'i') +all_float_types = sorted(tp for tp in all_primitive_types + if all_primitive_types[tp] == 'f') + +def all_signed_integer_types(ffi): + return [x for x in all_integer_types if int(ffi.cast(x, -1)) < 0] + +def all_unsigned_integer_types(ffi): + return [x for x in all_integer_types if int(ffi.cast(x, -1)) > 0] + + +def test_primitive_category(): + for typename in all_primitive_types: + tp = model.PrimitiveType(typename) + C = tp.is_char_type() + F = tp.is_float_type() + X = tp.is_complex_type() + I = tp.is_integer_type() + assert C == (typename in ('char', 'wchar_t', 'char16_t', 'char32_t')) + assert F == (typename in ('float', 'double', 'long double')) + assert X == (typename in ('float _Complex', 'double _Complex')) + assert I + F + C + X == 1 # one and only one of them is true + +def test_all_integer_and_float_types(): + typenames = [] + for typename in all_primitive_types: + if (all_primitive_types[typename] == 'c' or + all_primitive_types[typename] == 'j' or # complex + typename == '_Bool' or typename == 'long double'): + pass + else: + typenames.append(typename) + # + ffi = FFI() + ffi.cdef('\n'.join(["%s foo_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in typenames])) + lib = ffi.verify('\n'.join(["%s foo_%s(%s x) { return (%s)(x+1); }" % + (tp, tp.replace(' ', '_'), tp, tp) + for tp in typenames])) + for typename in typenames: + foo = getattr(lib, 'foo_%s' % typename.replace(' ', '_')) + assert foo(42) == 43 + if sys.version < '3': + assert foo(long(44)) == 45 + assert foo(ffi.cast(typename, 46)) == 47 + py.test.raises(TypeError, foo, ffi.NULL) + # + # check for overflow cases + if all_primitive_types[typename] == 'f': + continue + for value in [-2**80, -2**40, -2**20, -2**10, -2**5, -1, + 2**5, 2**10, 2**20, 2**40, 2**80]: + overflows = int(ffi.cast(typename, value)) != value + if overflows: + py.test.raises(OverflowError, foo, value) + else: + assert foo(value) == value + 1 + +def test_var_signed_integer_types(): + ffi = FFI() + lst = all_signed_integer_types(ffi) + csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + for tp in lst]) + ffi.cdef(csource) + lib = ffi.verify(csource) + for tp in lst: + varname = 'somevar_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + max = (1 << (8*sz-1)) - 1 + min = -(1 << (8*sz-1)) + setattr(lib, varname, max) + assert getattr(lib, varname) == max + setattr(lib, varname, min) + assert getattr(lib, varname) == min + py.test.raises(OverflowError, setattr, lib, varname, max+1) + py.test.raises(OverflowError, setattr, lib, varname, min-1) + +def test_var_unsigned_integer_types(): + ffi = FFI() + lst = all_unsigned_integer_types(ffi) + csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + for tp in lst]) + ffi.cdef(csource) + lib = ffi.verify(csource) + for tp in lst: + varname = 'somevar_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + if tp != '_Bool': + max = (1 << (8*sz)) - 1 + else: + max = 1 + setattr(lib, varname, max) + assert getattr(lib, varname) == max + setattr(lib, varname, 0) + assert getattr(lib, varname) == 0 + py.test.raises(OverflowError, setattr, lib, varname, max+1) + py.test.raises(OverflowError, setattr, lib, varname, -1) + +def test_fn_signed_integer_types(): + ffi = FFI() + lst = all_signed_integer_types(ffi) + cdefsrc = "\n".join(["%s somefn_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in lst]) + ffi.cdef(cdefsrc) + verifysrc = "\n".join(["%s somefn_%s(%s x) { return x; }" % + (tp, tp.replace(' ', '_'), tp) for tp in lst]) + lib = ffi.verify(verifysrc) + for tp in lst: + fnname = 'somefn_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + max = (1 << (8*sz-1)) - 1 + min = -(1 << (8*sz-1)) + fn = getattr(lib, fnname) + assert fn(max) == max + assert fn(min) == min + py.test.raises(OverflowError, fn, max + 1) + py.test.raises(OverflowError, fn, min - 1) + +def test_fn_unsigned_integer_types(): + ffi = FFI() + lst = all_unsigned_integer_types(ffi) + cdefsrc = "\n".join(["%s somefn_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in lst]) + ffi.cdef(cdefsrc) + verifysrc = "\n".join(["%s somefn_%s(%s x) { return x; }" % + (tp, tp.replace(' ', '_'), tp) for tp in lst]) + lib = ffi.verify(verifysrc) + for tp in lst: + fnname = 'somefn_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + if tp != '_Bool': + max = (1 << (8*sz)) - 1 + else: + max = 1 + fn = getattr(lib, fnname) + assert fn(max) == max + assert fn(0) == 0 + py.test.raises(OverflowError, fn, max + 1) + py.test.raises(OverflowError, fn, -1) + +def test_char_type(): + ffi = FFI() + ffi.cdef("char foo(char);") + lib = ffi.verify("char foo(char x) { return ++x; }") + assert lib.foo(b"A") == b"B" + py.test.raises(TypeError, lib.foo, b"bar") + py.test.raises(TypeError, lib.foo, "bar") + +def test_wchar_type(): + ffi = FFI() + if ffi.sizeof('wchar_t') == 2: + uniexample1 = u+'\u1234' + uniexample2 = u+'\u1235' + else: + uniexample1 = u+'\U00012345' + uniexample2 = u+'\U00012346' + # + ffi.cdef("wchar_t foo(wchar_t);") + lib = ffi.verify("wchar_t foo(wchar_t x) { return x+1; }") + assert lib.foo(uniexample1) == uniexample2 + +def test_char16_char32_type(): + py.test.skip("XXX test or fully prevent char16_t and char32_t from " + "working in ffi.verify() mode") + +def test_no_argument(): + ffi = FFI() + ffi.cdef("int foo(void);") + lib = ffi.verify("int foo(void) { return 42; }") + assert lib.foo() == 42 + +def test_two_arguments(): + ffi = FFI() + ffi.cdef("int foo(int, int);") + lib = ffi.verify("int foo(int a, int b) { return a - b; }") + assert lib.foo(40, -2) == 42 + +def test_macro(): + ffi = FFI() + ffi.cdef("int foo(int, int);") + lib = ffi.verify("#define foo(a, b) ((a) * (b))") + assert lib.foo(-6, -7) == 42 + +def test_ptr(): + ffi = FFI() + ffi.cdef("int *foo(int *);") + lib = ffi.verify("int *foo(int *a) { return a; }") + assert lib.foo(ffi.NULL) == ffi.NULL + p = ffi.new("int *", 42) + q = ffi.new("int *", 42) + assert lib.foo(p) == p + assert lib.foo(q) != p + +def test_bogus_ptr(): + ffi = FFI() + ffi.cdef("int *foo(int *);") + lib = ffi.verify("int *foo(int *a) { return a; }") + py.test.raises(TypeError, lib.foo, ffi.new("short *", 42)) + + +def test_verify_typedefs(): + py.test.skip("ignored so far") + types = ['signed char', 'unsigned char', 'int', 'long'] + for cdefed in types: + for real in types: + ffi = FFI() + ffi.cdef("typedef %s foo_t;" % cdefed) + if cdefed == real: + ffi.verify("typedef %s foo_t;" % real) + else: + py.test.raises(VerificationError, ffi.verify, + "typedef %s foo_t;" % real) + +def test_nondecl_struct(): + ffi = FFI() + ffi.cdef("typedef struct foo_s foo_t; int bar(foo_t *);") + lib = ffi.verify("typedef struct foo_s foo_t;\n" + "int bar(foo_t *f) { (void)f; return 42; }\n") + assert lib.bar(ffi.NULL) == 42 + +def test_ffi_full_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { char x; int y; long *z; };") + ffi.verify("struct foo_s { char x; int y; long *z; };") + # + if sys.platform != 'win32': # XXX fixme: only gives warnings + py.test.raises(VerificationError, ffi.verify, + "struct foo_s { char x; int y; int *z; };") + # + py.test.raises(VerificationError, ffi.verify, + "struct foo_s { int y; long *z; };") + # + e = py.test.raises(VerificationError, ffi.verify, + "struct foo_s { int y; char x; long *z; };") + assert str(e.value) == ( + "struct foo_s: wrong offset for field 'x'" + " (we have 0, but C compiler says 4)") + # + e = py.test.raises(VerificationError, ffi.verify, + "struct foo_s { char x; int y; long *z; char extra; };") + assert str(e.value) == ( + "struct foo_s: wrong total size" + " (we have %d, but C compiler says %d)" % ( + ffi.sizeof("struct foo_s"), + ffi.sizeof("struct foo_s") + ffi.sizeof("long*"))) + # + # a corner case that we cannot really detect, but where it has no + # bad consequences: the size is the same, but there is an extra field + # that replaces what is just padding in our declaration above + ffi.verify("struct foo_s { char x, extra; int y; long *z; };") + # + e = py.test.raises(VerificationError, ffi.verify, + "struct foo_s { char x; short pad; short y; long *z; };") + assert str(e.value) == ( + "struct foo_s: wrong size for field 'y'" + " (we have 4, but C compiler says 2)") + +def test_ffi_nonfull_struct(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { + int x; + ...; + }; + """) + py.test.raises(VerificationMissing, ffi.sizeof, 'struct foo_s') + py.test.raises(VerificationMissing, ffi.offsetof, 'struct foo_s', 'x') + py.test.raises(VerificationMissing, ffi.new, 'struct foo_s *') + ffi.verify(""" + struct foo_s { + int a, b, x, c, d, e; + }; + """) + assert ffi.sizeof('struct foo_s') == 6 * ffi.sizeof('int') + assert ffi.offsetof('struct foo_s', 'x') == 2 * ffi.sizeof('int') + +def test_ffi_nonfull_alignment(): + ffi = FFI() + ffi.cdef("struct foo_s { char x; ...; };") + ffi.verify("struct foo_s { int a, b; char x; };") + assert ffi.sizeof('struct foo_s') == 3 * ffi.sizeof('int') + assert ffi.alignof('struct foo_s') == ffi.sizeof('int') + +def _check_field_match(typename, real, expect_mismatch): + ffi = FFI() + testing_by_size = (expect_mismatch == 'by_size') + if testing_by_size: + expect_mismatch = ffi.sizeof(typename) != ffi.sizeof(real) + ffi.cdef("struct foo_s { %s x; ...; };" % typename) + try: + ffi.verify("struct foo_s { %s x; };" % real) + except VerificationError: + if not expect_mismatch: + if testing_by_size and typename != real: + print("ignoring mismatch between %s* and %s* even though " + "they have the same size" % (typename, real)) + return + raise AssertionError("unexpected mismatch: %s should be accepted " + "as equal to %s" % (typename, real)) + else: + if expect_mismatch: + raise AssertionError("mismatch not detected: " + "%s != %s" % (typename, real)) + +def test_struct_bad_sized_integer(): + for typename in ['int8_t', 'int16_t', 'int32_t', 'int64_t']: + for real in ['int8_t', 'int16_t', 'int32_t', 'int64_t']: + _check_field_match(typename, real, "by_size") + +def test_struct_bad_sized_float(): + for typename in all_float_types: + for real in all_float_types: + _check_field_match(typename, real, "by_size") + +def test_struct_signedness_ignored(): + _check_field_match("int", "unsigned int", expect_mismatch=False) + _check_field_match("unsigned short", "signed short", expect_mismatch=False) + +def test_struct_float_vs_int(): + if sys.platform == 'win32': + py.test.skip("XXX fixme: only gives warnings") + ffi = FFI() + for typename in all_signed_integer_types(ffi): + for real in all_float_types: + _check_field_match(typename, real, expect_mismatch=True) + for typename in all_float_types: + for real in all_signed_integer_types(ffi): + _check_field_match(typename, real, expect_mismatch=True) + +def test_struct_array_field(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[17]; ...; };") + ffi.verify("struct foo_s { int x; int a[17]; int y; };") + assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int') + +def test_struct_array_no_length(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[]; int y; ...; };\n" + "int bar(struct foo_s *);\n") + lib = ffi.verify("struct foo_s { int x; int a[17]; int y; };\n" + "int bar(struct foo_s *f) { return f->a[14]; }\n") + assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.typeof(s.a) is ffi.typeof('int[]') # implicit max length + assert len(s.a) == 18 # max length, computed from the size and start offset + s.a[14] = 4242 + assert lib.bar(s) == 4242 + # with no declared length, out-of-bound accesses are not detected + s.a[17] = -521 + assert s.y == s.a[17] == -521 + # + s = ffi.new("struct foo_s *", {'a': list(range(17))}) + assert s.a[16] == 16 + # overflows at construction time not detected either + s = ffi.new("struct foo_s *", {'a': list(range(18))}) + assert s.y == s.a[17] == 17 + +def test_struct_array_guess_length(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[...]; };") + ffi.verify("struct foo_s { int x; int a[17]; int y; };") + assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int') + py.test.raises(IndexError, 's.a[17]') + +def test_struct_array_c99_1(): + if sys.platform == 'win32': + py.test.skip("requires C99") + ffi = FFI() + ffi.cdef("struct foo_s { int x; int a[]; };") + ffi.verify("struct foo_s { int x; int a[]; };") + assert ffi.sizeof('struct foo_s') == 1 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242, 4]) + assert ffi.sizeof(ffi.typeof(s[0])) == 1 * ffi.sizeof('int') + assert ffi.sizeof(s[0]) == 5 * ffi.sizeof('int') + # ^^^ explanation: if you write in C: "char x[5];", then + # "sizeof(x)" will evaluate to 5. The behavior above is + # a generalization of that to "struct foo_s[len(a)=5] x;" + # if you could do that in C. + assert s.a[3] == 0 + s = ffi.new("struct foo_s *", [424242, [-40, -30, -20, -10]]) + assert ffi.sizeof(s[0]) == 5 * ffi.sizeof('int') + assert s.a[3] == -10 + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242]) + assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int') + +def test_struct_array_c99_2(): + if sys.platform == 'win32': + py.test.skip("requires C99") + ffi = FFI() + ffi.cdef("struct foo_s { int x; int a[]; ...; };") + ffi.verify("struct foo_s { int x, y; int a[]; };") + assert ffi.sizeof('struct foo_s') == 2 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242, 4]) + assert ffi.sizeof(s[0]) == 6 * ffi.sizeof('int') + assert s.a[3] == 0 + s = ffi.new("struct foo_s *", [424242, [-40, -30, -20, -10]]) + assert ffi.sizeof(s[0]) == 6 * ffi.sizeof('int') + assert s.a[3] == -10 + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242]) + assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int') + +def test_struct_ptr_to_array_field(): + ffi = FFI() + ffi.cdef("struct foo_s { int (*a)[17]; ...; }; struct bar_s { ...; };") + ffi.verify("struct foo_s { int x; int (*a)[17]; int y; };\n" + "struct bar_s { int x; int *a; int y; };") + assert ffi.sizeof('struct foo_s') == ffi.sizeof("struct bar_s") + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s.a) == ffi.sizeof('int(*)[17]') == ffi.sizeof("int *") + +def test_struct_with_bitfield_exact(): + ffi = FFI() + ffi.cdef("struct foo_s { int a:2, b:3; };") + ffi.verify("struct foo_s { int a:2, b:3; };") + s = ffi.new("struct foo_s *") + s.b = 3 + py.test.raises(OverflowError, "s.b = 4") + assert s.b == 3 + +def test_struct_with_bitfield_enum(): + ffi = FFI() + code = """ + typedef enum { AA, BB, CC } foo_e; + typedef struct { foo_e f:2; } foo_s; + """ + ffi.cdef(code) + ffi.verify(code) + s = ffi.new("foo_s *") + s.f = 2 + assert s.f == 2 + +def test_unsupported_struct_with_bitfield_ellipsis(): + ffi = FFI() + py.test.raises(NotImplementedError, ffi.cdef, + "struct foo_s { int a:2, b:3; ...; };") + +def test_global_constants(): + ffi = FFI() + # use 'static const int', as generally documented, although in this + # case the 'static' is completely ignored. + ffi.cdef("static const int AA, BB, CC, DD;") + lib = ffi.verify("#define AA 42\n" + "#define BB (-43) // blah\n" + "#define CC (22*2) /* foobar */\n" + "#define DD ((unsigned int)142) /* foo\nbar */\n") + assert lib.AA == 42 + assert lib.BB == -43 + assert lib.CC == 44 + assert lib.DD == 142 + +def test_global_const_int_size(): + # integer constants: ignore the declared type, always just use the value + for value in [-2**63, -2**31, -2**15, + 2**15-1, 2**15, 2**31-1, 2**31, 2**32-1, 2**32, + 2**63-1, 2**63, 2**64-1]: + ffi = FFI() + if value == int(ffi.cast("long long", value)): + if value < 0: + vstr = '(-%dLL-1)' % (~value,) + else: + vstr = '%dLL' % value + elif value == int(ffi.cast("unsigned long long", value)): + vstr = '%dULL' % value + else: + raise AssertionError(value) + ffi.cdef("static const unsigned short AA;") + lib = ffi.verify("#define AA %s\n" % vstr) + assert lib.AA == value + assert type(lib.AA) is type(int(lib.AA)) + +def test_global_constants_non_int(): + ffi = FFI() + ffi.cdef("static char *const PP;") + lib = ffi.verify('static char *const PP = "testing!";\n') + assert ffi.typeof(lib.PP) == ffi.typeof("char *") + assert ffi.string(lib.PP) == b"testing!" + +def test_nonfull_enum(): + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2, EE3, ... \n \t };") + py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE2') + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == "EE2" + assert ffi.string(ffi.cast('enum ee', -10)) == "EE3" + # + # try again + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == "EE2" + # + assert ffi.typeof("enum ee").relements == {'EE1': 10, 'EE2': 11, 'EE3': -10} + assert ffi.typeof("enum ee").elements == {10: 'EE1', 11: 'EE2', -10: 'EE3'} + +def test_full_enum(): + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2, EE3 };") + ffi.verify("enum ee { EE1, EE2, EE3 };") + py.test.raises(VerificationError, ffi.verify, "enum ee { EE1, EE2 };") + e = py.test.raises(VerificationError, ffi.verify, + "enum ee { EE1, EE3, EE2 };") + assert str(e.value) == 'enum ee: EE2 has the real value 2, not 1' + # extra items cannot be seen and have no bad consequence anyway + lib = ffi.verify("enum ee { EE1, EE2, EE3, EE4 };") + assert lib.EE3 == 2 + +def test_enum_usage(): + ffi = FFI() + ffi.cdef("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;") + lib = ffi.verify("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;") + assert lib.EE2 == 1 + s = ffi.new("sp", [lib.EE2]) + assert s.x == 1 + s.x = 17 + assert s.x == 17 + +def test_anonymous_enum(): + ffi = FFI() + ffi.cdef("enum { EE1 }; enum { EE2, EE3 };") + lib = ffi.verify("enum { EE1 }; enum { EE2, EE3 };") + assert lib.EE1 == 0 + assert lib.EE2 == 0 + assert lib.EE3 == 1 + +def test_nonfull_anonymous_enum(): + ffi = FFI() + ffi.cdef("enum { EE1, ... }; enum { EE3, ... };") + lib = ffi.verify("enum { EE2, EE1 }; enum { EE3 };") + assert lib.EE1 == 1 + assert lib.EE3 == 0 + +def test_nonfull_enum_syntax2(): + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2=\t..., EE3 };") + py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1') + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2' + assert ffi.string(ffi.cast('enum ee', -10)) == 'EE3' + # + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2=\t... };") + py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1') + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2' + # + ffi = FFI() + ffi.cdef("enum ee2 { EE4=..., EE5=..., ... };") + ffi.verify("enum ee2 { EE4=-1234-5, EE5 }; ") + assert ffi.string(ffi.cast('enum ee2', -1239)) == 'EE4' + assert ffi.string(ffi.cast('enum ee2', -1238)) == 'EE5' + +def test_nonfull_enum_bug3(): + ffi = FFI() + ffi.cdef("enum ee2 { EE4=..., EE5=... };") + ffi.cdef("enum ee6 { EE7=10, EE8=..., EE9=... };") + +def test_get_set_errno(): + ffi = FFI() + ffi.cdef("int foo(int);") + lib = ffi.verify(""" + static int foo(int x) + { + errno += 1; + return x * 7; + } + """) + ffi.errno = 15 + assert lib.foo(6) == 42 + assert ffi.errno == 16 + +def test_define_int(): + ffi = FFI() + ffi.cdef("#define FOO ...\n" + "\t#\tdefine\tBAR\t...\t\n" + "#define BAZ ...\n") + lib = ffi.verify("#define FOO 42\n" + "#define BAR (-44)\n" + "#define BAZ 0xffffffffffffffffULL\n") + assert lib.FOO == 42 + assert lib.BAR == -44 + assert lib.BAZ == 0xffffffffffffffff + +def test_access_variable(): + ffi = FFI() + ffi.cdef("int foo(void);\n" + "int somenumber;") + lib = ffi.verify(""" + static int somenumber = 2; + static int foo(void) { + return somenumber * 7; + } + """) + assert lib.somenumber == 2 + assert lib.foo() == 14 + lib.somenumber = -6 + assert lib.foo() == -42 + assert lib.somenumber == -6 + lib.somenumber = 2 # reset for the next run, if any + +def test_access_address_of_variable(): + # access the address of 'somenumber': need a trick + ffi = FFI() + ffi.cdef("int somenumber; static int *const somenumberptr;") + lib = ffi.verify(""" + static int somenumber = 2; + #define somenumberptr (&somenumber) + """) + assert lib.somenumber == 2 + lib.somenumberptr[0] = 42 + assert lib.somenumber == 42 + lib.somenumber = 2 # reset for the next run, if any + +def test_access_array_variable(length=5): + ffi = FFI() + ffi.cdef("int foo(int);\n" + "int somenumber[%s];" % (length,)) + lib = ffi.verify(""" + static int somenumber[] = {2, 2, 3, 4, 5}; + static int foo(int i) { + return somenumber[i] * 7; + } + """) + if length == '': + # a global variable of an unknown array length is implicitly + # transformed into a global pointer variable, because we can only + # work with array instances whose length we know. using a pointer + # instead of an array gives the correct effects. + assert repr(lib.somenumber).startswith("x * 7; } + """) + f = ffi.new("foo_t *") + f.x = 6 + assert lib.foo(f) == 42 + +def test_unknown_type(): + ffi = FFI() + ffi.cdef(""" + typedef ... token_t; + int foo(token_t *); + #define TOKEN_SIZE ... + """) + lib = ffi.verify(""" + typedef float token_t; + static int foo(token_t *tk) { + if (!tk) + return -42; + *tk += 1.601f; + return (int)*tk; + } + #define TOKEN_SIZE sizeof(token_t) + """) + # we cannot let ffi.new("token_t *") work, because we don't know ahead of + # time if it's ok to ask 'sizeof(token_t)' in the C code or not. + # See test_unknown_type_2. Workaround. + tkmem = ffi.new("char[]", lib.TOKEN_SIZE) # zero-initialized + tk = ffi.cast("token_t *", tkmem) + results = [lib.foo(tk) for i in range(6)] + assert results == [1, 3, 4, 6, 8, 9] + assert lib.foo(ffi.NULL) == -42 + +def test_unknown_type_2(): + ffi = FFI() + ffi.cdef("typedef ... token_t;") + lib = ffi.verify("typedef struct token_s token_t;") + # assert did not crash, even though 'sizeof(token_t)' is not valid in C. + +def test_unknown_type_3(): + ffi = FFI() + ffi.cdef(""" + typedef ... *token_p; + token_p foo(token_p); + """) + lib = ffi.verify(""" + typedef struct _token_s *token_p; + token_p foo(token_p arg) { + if (arg) + return (token_p)0x12347; + else + return (token_p)0x12345; + } + """) + p = lib.foo(ffi.NULL) + assert int(ffi.cast("intptr_t", p)) == 0x12345 + q = lib.foo(p) + assert int(ffi.cast("intptr_t", q)) == 0x12347 + +def test_varargs(): + ffi = FFI() + ffi.cdef("int foo(int x, ...);") + lib = ffi.verify(""" + int foo(int x, ...) { + va_list vargs; + va_start(vargs, x); + x -= va_arg(vargs, int); + x -= va_arg(vargs, int); + va_end(vargs); + return x; + } + """) + assert lib.foo(50, ffi.cast("int", 5), ffi.cast("int", 3)) == 42 + +def test_varargs_exact(): + if sys.platform == 'win32': + py.test.skip("XXX fixme: only gives warnings") + ffi = FFI() + ffi.cdef("int foo(int x, ...);") + py.test.raises(VerificationError, ffi.verify, """ + int foo(long long x, ...) { + return x; + } + """) + +def test_varargs_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { char a; int b; }; int foo(int x, ...);") + lib = ffi.verify(""" + struct foo_s { + char a; int b; + }; + int foo(int x, ...) { + va_list vargs; + struct foo_s s; + va_start(vargs, x); + s = va_arg(vargs, struct foo_s); + va_end(vargs); + return s.a - s.b; + } + """) + s = ffi.new("struct foo_s *", [b'B', 1]) + assert lib.foo(50, s[0]) == ord('A') + +def test_autofilled_struct_as_argument(): + ffi = FFI() + ffi.cdef("struct foo_s { long a; double b; ...; };\n" + "int foo(struct foo_s);") + lib = ffi.verify(""" + struct foo_s { + double b; + long a; + }; + int foo(struct foo_s s) { + return (int)s.a - (int)s.b; + } + """) + s = ffi.new("struct foo_s *", [100, 1]) + assert lib.foo(s[0]) == 99 + assert lib.foo([100, 1]) == 99 + +def test_autofilled_struct_as_argument_dynamic(): + ffi = FFI() + ffi.cdef("struct foo_s { long a; ...; };\n" + "int (*foo)(struct foo_s);") + lib = ffi.verify(""" + struct foo_s { + double b; + long a; + }; + int foo1(struct foo_s s) { + return (int)s.a - (int)s.b; + } + 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; 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(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { int aa, bb; }; + struct foo_s foo(int a, int b); + """) + lib = ffi.verify(""" + struct foo_s { int aa, bb; }; + struct foo_s foo(int a, int b) { + struct foo_s r; + r.aa = a*a; + r.bb = b*b; + return r; + } + """) + s = lib.foo(6, 7) + assert repr(s) == "" + assert s.aa == 36 + assert s.bb == 49 + +def test_func_as_funcptr(): + ffi = FFI() + ffi.cdef("int *(*const fooptr)(void);") + lib = ffi.verify(""" + int *foo(void) { + return (int*)"foobar"; + } + int *(*fooptr)(void) = foo; + """) + foochar = ffi.cast("char *(*)(void)", lib.fooptr) + s = foochar() + assert ffi.string(s) == b"foobar" + +def test_funcptr_as_argument(): + ffi = FFI() + ffi.cdef(""" + void qsort(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *)); + """) + ffi.verify("#include ") + +def test_func_as_argument(): + ffi = FFI() + ffi.cdef(""" + void qsort(void *base, size_t nel, size_t width, + int compar(const void *, const void *)); + """) + ffi.verify("#include ") + +def test_array_as_argument(): + ffi = FFI() + ffi.cdef(""" + size_t strlen(char string[]); + """) + ffi.verify("#include ") + +def test_enum_as_argument(): + ffi = FFI() + ffi.cdef(""" + enum foo_e { AA, BB, ... }; + int foo_func(enum foo_e); + """) + lib = ffi.verify(""" + enum foo_e { AA, CC, BB }; + int foo_func(enum foo_e e) { return (int)e; } + """) + assert lib.foo_func(lib.BB) == 2 + py.test.raises(TypeError, lib.foo_func, "BB") + +def test_enum_as_function_result(): + ffi = FFI() + ffi.cdef(""" + enum foo_e { AA, BB, ... }; + enum foo_e foo_func(int x); + """) + lib = ffi.verify(""" + enum foo_e { AA, CC, BB }; + enum foo_e foo_func(int x) { return (enum foo_e)x; } + """) + assert lib.foo_func(lib.BB) == lib.BB == 2 + +def test_enum_values(): + ffi = FFI() + ffi.cdef("enum enum1_e { AA, BB };") + lib = ffi.verify("enum enum1_e { AA, BB };") + assert lib.AA == 0 + assert lib.BB == 1 + assert ffi.string(ffi.cast("enum enum1_e", 1)) == 'BB' + +def test_typedef_complete_enum(): + ffi = FFI() + ffi.cdef("typedef enum { AA, BB } enum1_t;") + lib = ffi.verify("typedef enum { AA, BB } enum1_t;") + assert ffi.string(ffi.cast("enum1_t", 1)) == 'BB' + assert lib.AA == 0 + assert lib.BB == 1 + +def test_typedef_broken_complete_enum(): + ffi = FFI() + ffi.cdef("typedef enum { AA, BB } enum1_t;") + py.test.raises(VerificationError, ffi.verify, + "typedef enum { AA, CC, BB } enum1_t;") + +def test_typedef_incomplete_enum(): + ffi = FFI() + ffi.cdef("typedef enum { AA, BB, ... } enum1_t;") + lib = ffi.verify("typedef enum { AA, CC, BB } enum1_t;") + assert ffi.string(ffi.cast("enum1_t", 1)) == '1' + assert ffi.string(ffi.cast("enum1_t", 2)) == 'BB' + assert lib.AA == 0 + assert lib.BB == 2 + +def test_typedef_enum_as_argument(): + ffi = FFI() + ffi.cdef(""" + typedef enum { AA, BB, ... } foo_t; + int foo_func(foo_t); + """) + lib = ffi.verify(""" + typedef enum { AA, CC, BB } foo_t; + int foo_func(foo_t e) { return (int)e; } + """) + assert lib.foo_func(lib.BB) == lib.BB == 2 + py.test.raises(TypeError, lib.foo_func, "BB") + +def test_typedef_enum_as_function_result(): + ffi = FFI() + ffi.cdef(""" + typedef enum { AA, BB, ... } foo_t; + foo_t foo_func(int x); + """) + lib = ffi.verify(""" + typedef enum { AA, CC, BB } foo_t; + foo_t foo_func(int x) { return (foo_t)x; } + """) + assert lib.foo_func(lib.BB) == lib.BB == 2 + +def test_function_typedef(): + ffi = FFI() + ffi.cdef(""" + typedef double func_t(double); + func_t sin; + """) + lib = ffi.verify('#include ', libraries=lib_m) + assert lib.sin(1.23) == math.sin(1.23) + +def test_opaque_integer_as_function_result(): + #import platform + #if platform.machine().startswith('sparc'): + # py.test.skip('Breaks horribly on sparc (SIGILL + corrupted stack)') + #elif platform.machine() == 'mips64' and sys.maxsize > 2**32: + # py.test.skip('Segfaults on mips64el') + # XXX bad abuse of "struct { ...; }". It only works a bit by chance + # anyway. XXX think about something better :-( + ffi = FFI() + ffi.cdef(""" + typedef struct { ...; } myhandle_t; + myhandle_t foo(void); + """) + lib = ffi.verify(""" + typedef short myhandle_t; + myhandle_t foo(void) { return 42; } + """) + h = lib.foo() + assert ffi.sizeof(h) == ffi.sizeof("short") + +def test_return_partial_struct(): + ffi = FFI() + ffi.cdef(""" + typedef struct { int x; ...; } foo_t; + foo_t foo(void); + """) + lib = ffi.verify(""" + typedef struct { int y, x; } foo_t; + foo_t foo(void) { foo_t r = { 45, 81 }; return r; } + """) + h = lib.foo() + assert ffi.sizeof(h) == 2 * ffi.sizeof("int") + assert h.x == 81 + +def test_take_and_return_partial_structs(): + ffi = FFI() + ffi.cdef(""" + typedef struct { int x; ...; } foo_t; + foo_t foo(foo_t, foo_t); + """) + lib = ffi.verify(""" + typedef struct { int y, x; } foo_t; + foo_t foo(foo_t a, foo_t b) { + foo_t r = { 100, a.x * 5 + b.x * 7 }; + return r; + } + """) + args = ffi.new("foo_t[3]") + args[0].x = 1000 + args[2].x = -498 + h = lib.foo(args[0], args[2]) + assert ffi.sizeof(h) == 2 * ffi.sizeof("int") + assert h.x == 1000 * 5 - 498 * 7 + +def test_cannot_name_struct_type(): + ffi = FFI() + ffi.cdef("typedef struct { int x; } **sp; void foo(sp);") + e = py.test.raises(VerificationError, ffi.verify, + "typedef struct { int x; } **sp; void foo(sp x) { }") + assert 'in argument of foo: unknown type name' in str(e.value) + +def test_dont_check_unnamable_fields(): + ffi = FFI() + ffi.cdef("struct foo_s { struct { int x; } someone; };") + ffi.verify("struct foo_s { struct { int x; } someone; };") + # assert did not crash + +def test_nested_anonymous_struct_exact(): + if sys.platform == 'win32': + py.test.skip("nested anonymous struct/union") + ffi = FFI() + ffi.cdef(""" + struct foo_s { struct { int a; char b; }; union { char c, d; }; }; + """) + ffi.verify(""" + struct foo_s { struct { int a; char b; }; union { char c, d; }; }; + """) + p = ffi.new("struct foo_s *") + assert ffi.sizeof(p[0]) == 3 * ffi.sizeof("int") # with alignment + p.a = 1234567 + p.b = b'X' + p.c = b'Y' + assert p.a == 1234567 + assert p.b == b'X' + assert p.c == b'Y' + assert p.d == b'Y' + +def test_nested_anonymous_struct_exact_error(): + if sys.platform == 'win32': + py.test.skip("nested anonymous struct/union") + ffi = FFI() + ffi.cdef(""" + struct foo_s { struct { int a; char b; }; union { char c, d; }; }; + """) + py.test.raises(VerificationError, ffi.verify, """ + struct foo_s { struct { int a; short b; }; union { char c, d; }; }; + """) + py.test.raises(VerificationError, ffi.verify, """ + struct foo_s { struct { int a; char e, b; }; union { char c, d; }; }; + """) + +def test_nested_anonymous_struct_inexact_1(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { struct { char b; ...; }; union { char c, d; }; }; + """) + ffi.verify(""" + struct foo_s { int a, padding; char c, d, b; }; + """) + assert ffi.sizeof("struct foo_s") == 3 * ffi.sizeof("int") + +def test_nested_anonymous_struct_inexact_2(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { union { char c, d; }; struct { int a; char b; }; ...; }; + """) + ffi.verify(""" + struct foo_s { int a, padding; char c, d, b; }; + """) + assert ffi.sizeof("struct foo_s") == 3 * ffi.sizeof("int") + +def test_ffi_union(): + ffi = FFI() + ffi.cdef("union foo_u { char x; long *z; };") + ffi.verify("union foo_u { char x; int y; long *z; };") + +def test_ffi_union_partial(): + ffi = FFI() + ffi.cdef("union foo_u { char x; ...; };") + ffi.verify("union foo_u { char x; int y; };") + assert ffi.sizeof("union foo_u") == 4 + +def test_ffi_union_with_partial_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { int x; ...; }; union foo_u { struct foo_s s; };") + ffi.verify("struct foo_s { int a; int x; }; " + "union foo_u { char b[32]; struct foo_s s; };") + assert ffi.sizeof("struct foo_s") == 8 + assert ffi.sizeof("union foo_u") == 32 + +def test_ffi_union_partial_2(): + ffi = FFI() + ffi.cdef("typedef union { char x; ...; } u1;") + ffi.verify("typedef union { char x; int y; } u1;") + assert ffi.sizeof("u1") == 4 + +def test_ffi_union_with_partial_struct_2(): + ffi = FFI() + ffi.cdef("typedef struct { int x; ...; } s1;" + "typedef union { s1 s; } u1;") + ffi.verify("typedef struct { int a; int x; } s1; " + "typedef union { char b[32]; s1 s; } u1;") + assert ffi.sizeof("s1") == 8 + assert ffi.sizeof("u1") == 32 + assert ffi.offsetof("u1", "s") == 0 + +def test_ffi_struct_packed(): + if sys.platform == 'win32': + py.test.skip("needs a GCC extension") + ffi = FFI() + ffi.cdef("struct foo_s { int b; ...; };") + ffi.verify(""" + struct foo_s { + char a; + int b; + } __attribute__((packed)); + """) + +def test_tmpdir(): + import tempfile, os + from testing.udir import udir + tmpdir = tempfile.mkdtemp(dir=str(udir)) + ffi = FFI() + ffi.cdef("int foo(int);") + lib = ffi.verify("int foo(int a) { return a + 42; }", tmpdir=tmpdir) + assert os.listdir(tmpdir) + assert lib.foo(100) == 142 + +def test_relative_to(): + import tempfile, os + from testing.udir import udir + tmpdir = tempfile.mkdtemp(dir=str(udir)) + ffi = FFI() + ffi.cdef("int foo(int);") + f = open(os.path.join(tmpdir, 'foo.h'), 'w') + f.write("int foo(int a) { return a + 42; }\n") + f.close() + lib = ffi.verify('#include "foo.h"', + include_dirs=['.'], + relative_to=os.path.join(tmpdir, 'x')) + assert lib.foo(100) == 142 + +def test_bug1(): + ffi = FFI() + ffi.cdef(""" + typedef struct tdlhandle_s { ...; } *tdl_handle_t; + typedef struct my_error_code_ { + tdl_handle_t *rh; + } my_error_code_t; + """) + ffi.verify(""" + typedef struct tdlhandle_s { int foo; } *tdl_handle_t; + typedef struct my_error_code_ { + tdl_handle_t *rh; + } my_error_code_t; + """) + +def test_bool(): + if sys.platform == 'win32': + py.test.skip("_Bool not in MSVC") + ffi = FFI() + ffi.cdef("struct foo_s { _Bool x; };" + "_Bool foo(_Bool); _Bool (*foop)(_Bool);") + lib = ffi.verify(""" + struct foo_s { _Bool x; }; + int foo(int arg) { + return !arg; + } + _Bool _foofunc(_Bool x) { + return !x; + } + _Bool (*foop)(_Bool) = _foofunc; + """) + p = ffi.new("struct foo_s *") + p.x = 1 + assert p.x is True + py.test.raises(OverflowError, "p.x = -1") + py.test.raises(TypeError, "p.x = 0.0") + assert lib.foop(1) is False + assert lib.foop(True) is False + assert lib.foop(0) is True + py.test.raises(OverflowError, lib.foop, 42) + py.test.raises(TypeError, lib.foop, 0.0) + assert lib.foo(1) is False + assert lib.foo(True) is False + assert lib.foo(0) is True + py.test.raises(OverflowError, lib.foo, 42) + py.test.raises(TypeError, lib.foo, 0.0) + assert int(ffi.cast("_Bool", long(1))) == 1 + assert int(ffi.cast("_Bool", long(0))) == 0 + assert int(ffi.cast("_Bool", long(-1))) == 1 + assert int(ffi.cast("_Bool", 10**200)) == 1 + assert int(ffi.cast("_Bool", 10**40000)) == 1 + # + class Foo(object): + def __int__(self): + self.seen = 1 + return result + f = Foo() + f.seen = 0 + result = 42 + assert int(ffi.cast("_Bool", f)) == 1 + assert f.seen + f.seen = 0 + result = 0 + assert int(ffi.cast("_Bool", f)) == 0 + assert f.seen + # + py.test.raises(TypeError, ffi.cast, "_Bool", []) + +def test_bool_on_long_double(): + if sys.platform == 'win32': + py.test.skip("_Bool not in MSVC") + f = 1E-250 + if f == 0.0 or f*f != 0.0: + py.test.skip("unexpected precision") + ffi = FFI() + ffi.cdef("long double square(long double f); _Bool opposite(_Bool);") + lib = ffi.verify("long double square(long double f) { return f*f; }\n" + "_Bool opposite(_Bool x) { return !x; }") + f0 = lib.square(0.0) + f2 = lib.square(f) + f3 = lib.square(f * 2.0) + if repr(f2) == repr(f3): + py.test.skip("long double doesn't have enough precision") + assert float(f0) == float(f2) == float(f3) == 0.0 # too tiny for 'double' + assert int(ffi.cast("_Bool", f2)) == 1 + assert int(ffi.cast("_Bool", f3)) == 1 + assert int(ffi.cast("_Bool", f0)) == 0 + py.test.raises(TypeError, lib.opposite, f2) + +def test_cannot_pass_float(): + for basetype in ['char', 'short', 'int', 'long', 'long long']: + for sign in ['signed', 'unsigned']: + type = '%s %s' % (sign, basetype) + ffi = FFI() + ffi.cdef("struct foo_s { %s x; };\n" + "int foo(%s);" % (type, type)) + lib = ffi.verify(""" + struct foo_s { %s x; }; + int foo(%s arg) { + return !arg; + } + """ % (type, type)) + p = ffi.new("struct foo_s *") + py.test.raises(TypeError, "p.x = 0.0") + assert lib.foo(42) == 0 + assert lib.foo(0) == 1 + py.test.raises(TypeError, lib.foo, 0.0) + +def test_cast_from_int_type_to_bool(): + ffi = FFI() + for basetype in ['char', 'short', 'int', 'long', 'long long']: + for sign in ['signed', 'unsigned']: + type = '%s %s' % (sign, basetype) + assert int(ffi.cast("_Bool", ffi.cast(type, 42))) == 1 + assert int(ffi.cast("bool", ffi.cast(type, 42))) == 1 + assert int(ffi.cast("_Bool", ffi.cast(type, 0))) == 0 + +def test_addressof(): + ffi = FFI() + ffi.cdef(""" + struct point_s { int x, y; }; + struct foo_s { int z; struct point_s point; }; + struct point_s sum_coord(struct point_s *); + """) + lib = ffi.verify(""" + struct point_s { int x, y; }; + struct foo_s { int z; struct point_s point; }; + struct point_s sum_coord(struct point_s *point) { + struct point_s r; + r.x = point->x + point->y; + r.y = point->x - point->y; + return r; + } + """) + p = ffi.new("struct foo_s *") + p.point.x = 16 + p.point.y = 9 + py.test.raises(TypeError, lib.sum_coord, p.point) + res = lib.sum_coord(ffi.addressof(p.point)) + assert res.x == 25 + assert res.y == 7 + res2 = lib.sum_coord(ffi.addressof(res)) + assert res2.x == 32 + assert res2.y == 18 + py.test.raises(TypeError, lib.sum_coord, res2) + +def test_callback_in_thread(): + if sys.platform == 'win32': + py.test.skip("pthread only") + import os, subprocess, imp + arg = os.path.join(os.path.dirname(__file__), 'callback_in_thread.py') + g = subprocess.Popen([sys.executable, arg, + os.path.dirname(imp.find_module('cffi')[1])]) + result = g.wait() + assert result == 0 + +def test_keepalive_lib(): + ffi = FFI() + ffi.cdef("int foobar(void);") + lib = ffi.verify("int foobar(void) { return 42; }") + func = lib.foobar + ffi_r = weakref.ref(ffi) + lib_r = weakref.ref(lib) + del ffi + import gc; gc.collect() # lib stays alive + assert lib_r() is not None + assert ffi_r() is not None + assert func() == 42 + +def test_keepalive_ffi(): + ffi = FFI() + ffi.cdef("int foobar(void);") + lib = ffi.verify("int foobar(void) { return 42; }") + func = lib.foobar + ffi_r = weakref.ref(ffi) + lib_r = weakref.ref(lib) + del lib + import gc; gc.collect() # ffi stays alive + assert ffi_r() is not None + assert lib_r() is not None + assert func() == 42 + +def test_FILE_stored_in_stdout(): + if not sys.platform.startswith('linux'): + py.test.skip("likely, we cannot assign to stdout") + ffi = FFI() + ffi.cdef("int printf(const char *, ...); FILE *setstdout(FILE *);") + lib = ffi.verify(""" + #include + FILE *setstdout(FILE *f) { + FILE *result = stdout; + stdout = f; + return result; + } + """) + import os + fdr, fdw = os.pipe() + fw1 = os.fdopen(fdw, 'wb', 256) + old_stdout = lib.setstdout(fw1) + try: + # + fw1.write(b"X") + r = lib.printf(b"hello, %d!\n", ffi.cast("int", 42)) + fw1.close() + assert r == len("hello, 42!\n") + # + finally: + lib.setstdout(old_stdout) + # + result = os.read(fdr, 256) + os.close(fdr) + # the 'X' might remain in the user-level buffer of 'fw1' and + # end up showing up after the 'hello, 42!\n' + assert result == b"Xhello, 42!\n" or result == b"hello, 42!\nX" + +def test_FILE_stored_explicitly(): + ffi = FFI() + ffi.cdef("int myprintf11(const char *, int); FILE *myfile;") + lib = ffi.verify(""" + #include + FILE *myfile; + int myprintf11(const char *out, int value) { + return fprintf(myfile, out, value); + } + """) + import os + fdr, fdw = os.pipe() + fw1 = os.fdopen(fdw, 'wb', 256) + lib.myfile = ffi.cast("FILE *", fw1) + # + fw1.write(b"X") + r = lib.myprintf11(b"hello, %d!\n", ffi.cast("int", 42)) + fw1.close() + assert r == len("hello, 42!\n") + # + result = os.read(fdr, 256) + os.close(fdr) + # the 'X' might remain in the user-level buffer of 'fw1' and + # end up showing up after the 'hello, 42!\n' + assert result == b"Xhello, 42!\n" or result == b"hello, 42!\nX" + +def test_global_array_with_missing_length(): + ffi = FFI() + ffi.cdef("int fooarray[];") + lib = ffi.verify("int fooarray[50];") + assert repr(lib.fooarray).startswith("x; }") + res = lib.myfunc(ffi2.new("foo_t *", {'x': 10})) + assert res == 420 + res = lib.myfunc(ffi1.new("foo_t *", {'x': -10})) + assert res == -420 + +def test_include_enum(): + ffi1 = FFI() + ffi1.cdef("enum foo_e { AA, ... };") + lib1 = ffi1.verify("enum foo_e { CC, BB, AA };") + ffi2 = FFI() + ffi2.include(ffi1) + ffi2.cdef("int myfunc(enum foo_e);") + lib2 = ffi2.verify("enum foo_e { CC, BB, AA };" + "int myfunc(enum foo_e x) { return (int)x; }") + res = lib2.myfunc(lib2.AA) + assert res == 2 + +def test_named_pointer_as_argument(): + ffi = FFI() + ffi.cdef("typedef struct { int x; } *mystruct_p;\n" + "mystruct_p ff5a(mystruct_p);") + lib = ffi.verify("typedef struct { int x; } *mystruct_p;\n" + "mystruct_p ff5a(mystruct_p p) { p->x += 40; return p; }") + p = ffi.new("mystruct_p", [-2]) + q = lib.ff5a(p) + assert q == p + assert p.x == 38 + +def test_enum_size(): + cases = [('123', 4, 4294967295), + ('4294967295U', 4, 4294967295), + ('-123', 4, -1), + ('-2147483647-1', 4, -1), + ] + if FFI().sizeof("long") == 8: + cases += [('4294967296L', 8, 2**64-1), + ('%dUL' % (2**64-1), 8, 2**64-1), + ('-2147483649L', 8, -1), + ('%dL-1L' % (1-2**63), 8, -1)] + for hidden_value, expected_size, expected_minus1 in cases: + if sys.platform == 'win32' and 'U' in hidden_value: + continue # skipped on Windows + ffi = FFI() + ffi.cdef("enum foo_e { AA, BB, ... };") + lib = ffi.verify("enum foo_e { AA, BB=%s };" % hidden_value) + assert lib.AA == 0 + assert lib.BB == eval(hidden_value.replace('U', '').replace('L', '')) + assert ffi.sizeof("enum foo_e") == expected_size + assert int(ffi.cast("enum foo_e", -1)) == expected_minus1 + # test with the large value hidden: + # disabled so far, doesn't work +## for hidden_value, expected_size, expected_minus1 in cases: +## ffi = FFI() +## ffi.cdef("enum foo_e { AA, BB, ... };") +## lib = ffi.verify("enum foo_e { AA, BB=%s };" % hidden_value) +## assert lib.AA == 0 +## assert ffi.sizeof("enum foo_e") == expected_size +## assert int(ffi.cast("enum foo_e", -1)) == expected_minus1 + +def test_enum_bug118(): + maxulong = 256 ** FFI().sizeof("unsigned long") - 1 + for c1, c2, c2c in [(0xffffffff, -1, ''), + (maxulong, -1, ''), + (-1, 0xffffffff, 'U'), + (-1, maxulong, 'UL')]: + if c2c and sys.platform == 'win32': + continue # enums may always be signed with MSVC + ffi = FFI() + ffi.cdef("enum foo_e { AA=%s };" % c1) + e = py.test.raises(VerificationError, ffi.verify, + "enum foo_e { AA=%s%s };" % (c2, c2c)) + assert str(e.value) == ('enum foo_e: AA has the real value %d, not %d' + % (c2, c1)) + +def test_string_to_voidp_arg(): + ffi = FFI() + ffi.cdef("int myfunc(void *);") + lib = ffi.verify("int myfunc(void *p) { return ((signed char *)p)[0]; }") + res = lib.myfunc(b"hi!") + assert res == ord(b"h") + p = ffi.new("char[]", b"gah") + res = lib.myfunc(p) + assert res == ord(b"g") + res = lib.myfunc(ffi.cast("void *", p)) + assert res == ord(b"g") + res = lib.myfunc(ffi.cast("int *", p)) + assert res == ord(b"g") + +def test_callback_indirection(): + ffi = FFI() + ffi.cdef(""" + int (*python_callback)(int how_many, int *values); + int (*const c_callback)(int,...); /* pass this ptr to C routines */ + int some_c_function(int(*cb)(int,...)); + """) + lib = ffi.verify(""" + #include + #ifdef _WIN32 + #include + #define alloca _alloca + #else + # ifdef __FreeBSD__ + # include + # else + # include + # endif + #endif + static int (*python_callback)(int how_many, int *values); + static int c_callback(int how_many, ...) { + va_list ap; + /* collect the "..." arguments into the values[] array */ + int i, *values = alloca((size_t)how_many * sizeof(int)); + va_start(ap, how_many); + for (i=0; i" + +def test_bug_const_char_ptr_array_1(): + ffi = FFI() + ffi.cdef("""const char *a[...];""") + lib = ffi.verify("""const char *a[5];""") + assert repr(ffi.typeof(lib.a)) == "" + +def test_bug_const_char_ptr_array_2(): + from cffi import FFI # ignore warnings + ffi = FFI() + ffi.cdef("""const int a[];""") + lib = ffi.verify("""const int a[5];""") + assert repr(ffi.typeof(lib.a)) == "" + +def _test_various_calls(force_libffi): + cdef_source = """ + int xvalue; + long long ivalue, rvalue; + float fvalue; + double dvalue; + long double Dvalue; + signed char tf_bb(signed char x, signed char c); + unsigned char tf_bB(signed char x, unsigned char c); + short tf_bh(signed char x, short c); + unsigned short tf_bH(signed char x, unsigned short c); + int tf_bi(signed char x, int c); + unsigned int tf_bI(signed char x, unsigned int c); + long tf_bl(signed char x, long c); + unsigned long tf_bL(signed char x, unsigned long c); + long long tf_bq(signed char x, long long c); + unsigned long long tf_bQ(signed char x, unsigned long long c); + float tf_bf(signed char x, float c); + double tf_bd(signed char x, double c); + long double tf_bD(signed char x, long double c); + """ + if force_libffi: + cdef_source = (cdef_source + .replace('tf_', '(*const tf_') + .replace('(signed char x', ')(signed char x')) + ffi = FFI() + ffi.cdef(cdef_source) + lib = ffi.verify(""" + int xvalue; + long long ivalue, rvalue; + float fvalue; + double dvalue; + long double Dvalue; + + typedef signed char b_t; + typedef unsigned char B_t; + typedef short h_t; + typedef unsigned short H_t; + typedef int i_t; + typedef unsigned int I_t; + typedef long l_t; + typedef unsigned long L_t; + typedef long long q_t; + typedef unsigned long long Q_t; + typedef float f_t; + typedef double d_t; + typedef long double D_t; + #define S(letter) xvalue = (int)x; letter##value = (letter##_t)c; + #define R(letter) return (letter##_t)rvalue; + + signed char tf_bb(signed char x, signed char c) { S(i) R(b) } + unsigned char tf_bB(signed char x, unsigned char c) { S(i) R(B) } + short tf_bh(signed char x, short c) { S(i) R(h) } + unsigned short tf_bH(signed char x, unsigned short c) { S(i) R(H) } + int tf_bi(signed char x, int c) { S(i) R(i) } + unsigned int tf_bI(signed char x, unsigned int c) { S(i) R(I) } + long tf_bl(signed char x, long c) { S(i) R(l) } + unsigned long tf_bL(signed char x, unsigned long c) { S(i) R(L) } + long long tf_bq(signed char x, long long c) { S(i) R(q) } + unsigned long long tf_bQ(signed char x, unsigned long long c) { S(i) R(Q) } + float tf_bf(signed char x, float c) { S(f) R(f) } + double tf_bd(signed char x, double c) { S(d) R(d) } + long double tf_bD(signed char x, long double c) { S(D) R(D) } + """) + lib.rvalue = 0x7182838485868788 + for kind, cname in [('b', 'signed char'), + ('B', 'unsigned char'), + ('h', 'short'), + ('H', 'unsigned short'), + ('i', 'int'), + ('I', 'unsigned int'), + ('l', 'long'), + ('L', 'unsigned long'), + ('q', 'long long'), + ('Q', 'unsigned long long'), + ('f', 'float'), + ('d', 'double'), + ('D', 'long double')]: + sign = +1 if 'unsigned' in cname else -1 + lib.xvalue = 0 + lib.ivalue = 0 + lib.fvalue = 0 + lib.dvalue = 0 + lib.Dvalue = 0 + fun = getattr(lib, 'tf_b' + kind) + res = fun(-42, sign * 99) + if kind == 'D': + res = float(res) + assert res == int(ffi.cast(cname, 0x7182838485868788)) + assert lib.xvalue == -42 + if kind in 'fdD': + assert float(getattr(lib, kind + 'value')) == -99.0 + else: + assert lib.ivalue == sign * 99 + +def test_various_calls_direct(): + _test_various_calls(force_libffi=False) + +def test_various_calls_libffi(): + _test_various_calls(force_libffi=True) + +def test_ptr_to_opaque(): + ffi = FFI() + ffi.cdef("typedef ... foo_t; int f1(foo_t*); foo_t *f2(int);") + lib = ffi.verify(""" + #include + typedef struct { int x; } foo_t; + int f1(foo_t* p) { + int x = p->x; + free(p); + return x; + } + foo_t *f2(int x) { + foo_t *p = malloc(sizeof(foo_t)); + p->x = x; + return p; + } + """) + p = lib.f2(42) + x = lib.f1(p) + assert x == 42 + +def _run_in_multiple_threads(test1): + test1() + import sys + try: + import thread + except ImportError: + import _thread as thread + errors = [] + def wrapper(lock): + try: + test1() + except: + errors.append(sys.exc_info()) + lock.release() + locks = [] + for i in range(10): + _lock = thread.allocate_lock() + _lock.acquire() + thread.start_new_thread(wrapper, (_lock,)) + locks.append(_lock) + for _lock in locks: + _lock.acquire() + if errors: + raise errors[0][1] + +def test_errno_working_even_with_pypys_jit(): + ffi = FFI() + ffi.cdef("int f(int);") + lib = ffi.verify(""" + #include + int f(int x) { return (errno = errno + x); } + """) + @_run_in_multiple_threads + def test1(): + ffi.errno = 0 + for i in range(10000): + e = lib.f(1) + assert e == i + 1 + assert ffi.errno == e + for i in range(10000): + ffi.errno = i + e = lib.f(42) + assert e == i + 42 + +def test_getlasterror_working_even_with_pypys_jit(): + if sys.platform != 'win32': + py.test.skip("win32-only test") + ffi = FFI() + ffi.cdef("void SetLastError(DWORD);") + lib = ffi.dlopen("Kernel32.dll") + @_run_in_multiple_threads + def test1(): + for i in range(10000): + n = (1 << 29) + i + lib.SetLastError(n) + assert ffi.getwinerror()[0] == n + +def test_verify_dlopen_flags(): + # Careful with RTLD_GLOBAL. If by chance the FFI is not deleted + # promptly, like on PyPy, then other tests may see the same + # exported symbols as well. So we must not export a simple name + # like 'foo'! + ffi1 = FFI() + ffi1.cdef("int foo_verify_dlopen_flags;") + + lib1 = ffi1.verify("int foo_verify_dlopen_flags;", + flags=ffi1.RTLD_GLOBAL | ffi1.RTLD_LAZY) + lib2 = get_second_lib() + + lib1.foo_verify_dlopen_flags = 42 + assert lib2.foo_verify_dlopen_flags == 42 + lib2.foo_verify_dlopen_flags += 1 + assert lib1.foo_verify_dlopen_flags == 43 + +def get_second_lib(): + # Hack, using modulename makes the test fail + ffi2 = FFI() + ffi2.cdef("int foo_verify_dlopen_flags;") + lib2 = ffi2.verify("int foo_verify_dlopen_flags;", + flags=ffi2.RTLD_GLOBAL | ffi2.RTLD_LAZY) + return lib2 + +def test_consider_not_implemented_function_type(): + ffi = FFI() + ffi.cdef("typedef union { int a; float b; } Data;" + "typedef struct { int a:2; } MyStr;" + "typedef void (*foofunc_t)(Data);" + "typedef Data (*bazfunc_t)(void);" + "typedef MyStr (*barfunc_t)(void);") + fooptr = ffi.cast("foofunc_t", 123) + bazptr = ffi.cast("bazfunc_t", 123) + barptr = ffi.cast("barfunc_t", 123) + # assert did not crash so far + e = py.test.raises(NotImplementedError, fooptr, ffi.new("Data *")) + assert str(e.value) == ( + "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' 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, 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() + ffi.cdef("#define ABA ...") + lib = ffi.verify("", define_macros=[('ABA', '42')]) + assert lib.ABA == 42 + +def test_implicit_unicode_on_windows(): + if sys.platform != 'win32': + py.test.skip("win32-only test") + ffi = FFI() + e = py.test.raises(FFIError, ffi.cdef, "int foo(LPTSTR);") + assert str(e.value) == ("The Windows type 'LPTSTR' is only available after" + " you call ffi.set_unicode()") + for with_unicode in [True, False]: + ffi = FFI() + ffi.set_unicode(with_unicode) + ffi.cdef(""" + DWORD GetModuleFileName(HMODULE hModule, LPTSTR lpFilename, + DWORD nSize); + """) + lib = ffi.verify(""" + #include + """, libraries=['Kernel32']) + outbuf = ffi.new("TCHAR[]", 200) + n = lib.GetModuleFileName(ffi.NULL, outbuf, 500) + assert 0 < n < 500 + for i in range(n): + #print repr(outbuf[i]) + assert ord(outbuf[i]) != 0 + assert ord(outbuf[n]) == 0 + assert ord(outbuf[0]) < 128 # should be a letter, or '\' + +def test_use_local_dir(): + ffi = FFI() + lib = ffi.verify("", modulename="test_use_local_dir") + this_dir = os.path.dirname(__file__) + pycache_files = os.listdir(os.path.join(this_dir, '__pycache__')) + assert any('test_use_local_dir' in s for s in pycache_files) + +def test_define_known_value(): + ffi = FFI() + ffi.cdef("#define FOO 0x123") + lib = ffi.verify("#define FOO 0x123") + assert lib.FOO == 0x123 + +def test_define_wrong_value(): + ffi = FFI() + ffi.cdef("#define FOO 123") + e = py.test.raises(VerificationError, ffi.verify, "#define FOO 124") + assert str(e.value).endswith("FOO has the real value 124, not 123") + +def test_static_const_int_known_value(): + ffi = FFI() + ffi.cdef("static const int FOO = 0x123;") + lib = ffi.verify("#define FOO 0x123") + assert lib.FOO == 0x123 + +def test_static_const_int_wrong_value(): + ffi = FFI() + ffi.cdef("static const int FOO = 123;") + e = py.test.raises(VerificationError, ffi.verify, "#define FOO 124") + assert str(e.value).endswith("FOO has the real value 124, not 123") + +def test_const_struct_global(): + ffi = FFI() + ffi.cdef("typedef struct { int x; ...; } T; const T myglob;") + lib = ffi.verify("typedef struct { double y; int x; } T;" + "const T myglob = { 0.1, 42 };") + assert ffi.typeof(lib.myglob) == ffi.typeof("T") + assert lib.myglob.x == 42 + +def test_dont_support_int_dotdotdot(): + ffi = FFI() + ffi.cdef("typedef int... t1;") + e = py.test.raises(VerificationError, ffi.verify, "") + assert str(e.value) == ("feature not supported with ffi.verify(), but only " + "with ffi.set_source(): 'typedef int... t1'") + ffi = FFI() + ffi.cdef("typedef double ... t1;") + e = py.test.raises(VerificationError, ffi.verify, "") + assert str(e.value) == ("feature not supported with ffi.verify(), but only " + "with ffi.set_source(): 'typedef float... t1'") + +def test_const_fields(): + ffi = FFI() + ffi.cdef("""struct foo_s { const int a; void *const b; };""") + ffi.verify("""struct foo_s { const int a; void *const b; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'a' + assert foo_s.fields[0][1].type is ffi.typeof("int") + assert foo_s.fields[1][0] == 'b' + assert foo_s.fields[1][1].type is ffi.typeof("void *") + +def test_win32_calling_convention_0(): + ffi = FFI() + ffi.cdef(""" + int call1(int(__cdecl *cb)(int)); + int (*const call2)(int(__stdcall *cb)(int)); + """) + lib = ffi.verify(r""" + #ifndef _MSC_VER + # define __stdcall /* nothing */ + #endif + int call1(int(*cb)(int)) { + int i, result = 0; + //printf("call1: cb = %p\n", cb); + for (i = 0; i < 1000; i++) + result += cb(i); + //printf("result = %d\n", result); + return result; + } + int call2(int(__stdcall *cb)(int)) { + int i, result = 0; + //printf("call2: cb = %p\n", cb); + for (i = 0; i < 1000; i++) + result += cb(-i); + //printf("result = %d\n", result); + return result; + } + """) + @ffi.callback("int(int)") + def cb1(x): + return x * 2 + @ffi.callback("int __stdcall(int)") + def cb2(x): + return x * 3 + #print 'cb1 =', cb1 + res = lib.call1(cb1) + assert res == 500*999*2 + #print 'cb2 =', cb2 + #print ffi.typeof(lib.call2) + #print 'call2 =', lib.call2 + res = lib.call2(cb2) + #print '...' + assert res == -500*999*3 + #print 'done' + if sys.platform == 'win32' and sys.maxsize < 2**32: + assert '__stdcall' in str(ffi.typeof(cb2)) + assert '__stdcall' not in str(ffi.typeof(cb1)) + py.test.raises(TypeError, lib.call1, cb2) + py.test.raises(TypeError, lib.call2, cb1) + else: + assert '__stdcall' not in str(ffi.typeof(cb2)) + assert ffi.typeof(cb2) is ffi.typeof(cb1) + +def test_win32_calling_convention_1(): + ffi = FFI() + ffi.cdef(""" + int __cdecl call1(int(__cdecl *cb)(int)); + int __stdcall call2(int(__stdcall *cb)(int)); + int (__cdecl *const cb1)(int); + int (__stdcall *const cb2)(int); + """) + lib = ffi.verify(r""" + #ifndef _MSC_VER + # define __cdecl + # define __stdcall + #endif + int __cdecl cb1(int x) { return x * 2; } + int __stdcall cb2(int x) { return x * 3; } + + int __cdecl call1(int(__cdecl *cb)(int)) { + int i, result = 0; + //printf("here1\n"); + //printf("cb = %p, cb1 = %p\n", cb, (void *)cb1); + for (i = 0; i < 1000; i++) + result += cb(i); + //printf("result = %d\n", result); + return result; + } + int __stdcall call2(int(__stdcall *cb)(int)) { + int i, result = 0; + //printf("here1\n"); + //printf("cb = %p, cb2 = %p\n", cb, (void *)cb2); + for (i = 0; i < 1000; i++) + result += cb(-i); + //printf("result = %d\n", result); + return result; + } + """) + assert lib.call1(lib.cb1) == 500*999*2 + assert lib.call2(lib.cb2) == -500*999*3 + +def test_win32_calling_convention_2(): + # any mistake in the declaration of plain function (including the + # precise argument types and, here, the calling convention) are + # automatically corrected. But this does not apply to the 'cb' + # function pointer argument. + ffi = FFI() + ffi.cdef(""" + int __stdcall call1(int(__cdecl *cb)(int)); + int __cdecl call2(int(__stdcall *cb)(int)); + int (__cdecl *const cb1)(int); + int (__stdcall *const cb2)(int); + """) + lib = ffi.verify(r""" + #ifndef _MSC_VER + # define __cdecl + # define __stdcall + #endif + int __cdecl call1(int(__cdecl *cb)(int)) { + int i, result = 0; + for (i = 0; i < 1000; i++) + result += cb(i); + return result; + } + int __stdcall call2(int(__stdcall *cb)(int)) { + int i, result = 0; + for (i = 0; i < 1000; i++) + result += cb(-i); + return result; + } + int __cdecl cb1(int x) { return x * 2; } + int __stdcall cb2(int x) { return x * 3; } + """) + assert lib.call1(lib.cb1) == 500*999*2 + assert lib.call2(lib.cb2) == -500*999*3 + +def test_win32_calling_convention_3(): + ffi = FFI() + ffi.cdef(""" + struct point { int x, y; }; + + int (*const cb1)(struct point); + int (__stdcall *const cb2)(struct point); + + struct point __stdcall call1(int(*cb)(struct point)); + struct point call2(int(__stdcall *cb)(struct point)); + """) + lib = ffi.verify(r""" + #ifndef _MSC_VER + # define __cdecl + # define __stdcall + #endif + struct point { int x, y; }; + int cb1(struct point pt) { return pt.x + 10 * pt.y; } + int __stdcall cb2(struct point pt) { return pt.x + 100 * pt.y; } + struct point __stdcall call1(int(__cdecl *cb)(struct point)) { + int i; + struct point result = { 0, 0 }; + //printf("here1\n"); + //printf("cb = %p, cb1 = %p\n", cb, (void *)cb1); + for (i = 0; i < 1000; i++) { + struct point p = { i, -i }; + int r = cb(p); + result.x += r; + result.y -= r; + } + return result; + } + struct point __cdecl call2(int(__stdcall *cb)(struct point)) { + int i; + struct point result = { 0, 0 }; + for (i = 0; i < 1000; i++) { + struct point p = { -i, i }; + int r = cb(p); + result.x += r; + result.y -= r; + } + return result; + } + """) + if sys.platform == 'win32' and sys.maxsize < 2**32: + py.test.raises(TypeError, lib.call1, lib.cb2) + py.test.raises(TypeError, lib.call2, lib.cb1) + pt = lib.call1(lib.cb1) + assert (pt.x, pt.y) == (-9*500*999, 9*500*999) + pt = lib.call2(lib.cb2) + assert (pt.x, pt.y) == (99*500*999, -99*500*999) + +def _only_test_on_linux_intel(): + if not sys.platform.startswith('linux'): + py.test.skip('only running the memory-intensive test on Linux') + import platform + machine = platform.machine() + if 'x86' not in machine and 'x64' not in machine: + py.test.skip('only running the memory-intensive test on x86/x64') + +def test_ffi_gc_size_arg(): + # with PyPy's GC, these calls to ffi.gc() would rapidly consume + # 40 GB of RAM without the third argument + _only_test_on_linux_intel() + ffi = FFI() + ffi.cdef("void *malloc(size_t); void free(void *);") + lib = ffi.verify(r""" + #include + """) + for i in range(2000): + p = lib.malloc(20*1024*1024) # 20 MB + p1 = ffi.cast("char *", p) + for j in range(0, 20*1024*1024, 4096): + p1[j] = b'!' + p = ffi.gc(p, lib.free, 20*1024*1024) + del p + +def test_ffi_gc_size_arg_2(): + # a variant of the above: this "attack" works on cpython's cyclic gc too + # and I found no obvious way to prevent that. So for now, this test + # is skipped on CPython, where it eats all the memory. + if '__pypy__' not in sys.builtin_module_names: + py.test.skip("find a way to tweak the cyclic GC of CPython") + _only_test_on_linux_intel() + ffi = FFI() + ffi.cdef("void *malloc(size_t); void free(void *);") + lib = ffi.verify(r""" + #include + """) + class X(object): + pass + for i in range(2000): + p = lib.malloc(50*1024*1024) # 50 MB + p1 = ffi.cast("char *", p) + for j in range(0, 50*1024*1024, 4096): + p1[j] = b'!' + p = ffi.gc(p, lib.free, 50*1024*1024) + x = X() + x.p = p + x.cyclic = x + del p, x + +def test_ffi_new_with_cycles(): + # still another variant, with ffi.new() + if '__pypy__' not in sys.builtin_module_names: + py.test.skip("find a way to tweak the cyclic GC of CPython") + ffi = FFI() + ffi.cdef("") + lib = ffi.verify("") + class X(object): + pass + for i in range(2000): + p = ffi.new("char[]", 50*1024*1024) # 50 MB + for j in range(0, 50*1024*1024, 4096): + p[j] = b'!' + x = X() + x.p = p + x.cyclic = x + del p, x diff --git a/testing/cffi0/test_verify2.py b/testing/cffi0/test_verify2.py new file mode 100644 index 0000000..a4af6d6 --- /dev/null +++ b/testing/cffi0/test_verify2.py @@ -0,0 +1,9 @@ +from .test_verify import * + +# This test file runs normally after test_verify. We only clean up the .c +# sources, to check that it also works when we have only the .so. The +# tests should run much faster than test_verify. + +def setup_module(): + import cffi.verifier + cffi.verifier.cleanup_tmpdir(keep_so=True) diff --git a/testing/cffi0/test_version.py b/testing/cffi0/test_version.py new file mode 100644 index 0000000..9325685 --- /dev/null +++ b/testing/cffi0/test_version.py @@ -0,0 +1,62 @@ +import py, os, sys +import cffi, _cffi_backend + +def setup_module(mod): + if '_cffi_backend' in sys.builtin_module_names: + py.test.skip("this is embedded version") + +#BACKEND_VERSIONS = { +# '0.4.2': '0.4', # did not change +# '0.7.1': '0.7', # did not change +# '0.7.2': '0.7', # did not change +# '0.8.1': '0.8', # did not change (essentially) +# '0.8.4': '0.8.3', # did not change +# } + +def test_version(): + v = cffi.__version__ + version_info = '.'.join(str(i) for i in cffi.__version_info__) + version_info = version_info.replace('.beta.', 'b') + version_info = version_info.replace('.plus', '+') + assert v == version_info + #v = BACKEND_VERSIONS.get(v, v) + assert v == _cffi_backend.__version__ + +def test_doc_version(): + parent = os.path.dirname(os.path.dirname(cffi.__file__)) + p = os.path.join(parent, 'doc', 'source', 'conf.py') + content = open(p).read() + # + v = cffi.__version__ + assert ("version = '%s'\n" % v[:4]) in content + assert ("release = '%s'\n" % v) in content + +def test_doc_version_file(): + parent = os.path.dirname(os.path.dirname(cffi.__file__)) + v = cffi.__version__.replace('+', '') + p = os.path.join(parent, 'doc', 'source', 'installation.rst') + content = open(p).read() + assert (" package version %s:" % v) in content + +def test_setup_version(): + parent = os.path.dirname(os.path.dirname(cffi.__file__)) + p = os.path.join(parent, 'setup.py') + content = open(p).read() + # + v = cffi.__version__.replace('+', '') + assert ("version='%s'" % v) in content + +def test_c_version(): + parent = os.path.dirname(os.path.dirname(cffi.__file__)) + v = cffi.__version__ + p = os.path.join(parent, 'c', 'test_c.py') + content = open(p).read() + #v = BACKEND_VERSIONS.get(v, v) + assert (('assert __version__ == "%s"' % v) in content) + +def test_embedding_h(): + parent = os.path.dirname(os.path.dirname(cffi.__file__)) + v = cffi.__version__ + p = os.path.join(parent, 'cffi', '_embedding.h') + content = open(p).read() + assert ('cffi version: %s"' % (v,)) in content diff --git a/testing/cffi0/test_vgen.py b/testing/cffi0/test_vgen.py new file mode 100644 index 0000000..1a7e05d --- /dev/null +++ b/testing/cffi0/test_vgen.py @@ -0,0 +1,12 @@ +import cffi.verifier +from .test_verify import * + + +def setup_module(): + cffi.verifier.cleanup_tmpdir() + cffi.verifier._FORCE_GENERIC_ENGINE = True + # Runs all tests with _FORCE_GENERIC_ENGINE = True, to make sure we + # also test vengine_gen.py. + +def teardown_module(): + cffi.verifier._FORCE_GENERIC_ENGINE = False diff --git a/testing/cffi0/test_vgen2.py b/testing/cffi0/test_vgen2.py new file mode 100644 index 0000000..34147c8 --- /dev/null +++ b/testing/cffi0/test_vgen2.py @@ -0,0 +1,13 @@ +import cffi.verifier +from .test_vgen import * + +# This test file runs normally after test_vgen. We only clean up the .c +# sources, to check that it also works when we have only the .so. The +# tests should run much faster than test_vgen. + +def setup_module(): + cffi.verifier.cleanup_tmpdir(keep_so=True) + cffi.verifier._FORCE_GENERIC_ENGINE = True + +def teardown_module(): + cffi.verifier._FORCE_GENERIC_ENGINE = False diff --git a/testing/cffi0/test_zdistutils.py b/testing/cffi0/test_zdistutils.py new file mode 100644 index 0000000..b67b105 --- /dev/null +++ b/testing/cffi0/test_zdistutils.py @@ -0,0 +1,288 @@ +import sys, os, imp, math, shutil +import py +from cffi import FFI, FFIError +from cffi.verifier import Verifier, _locate_engine_class, _get_so_suffixes +from cffi.ffiplatform import maybe_relative_path +from testing.udir import udir + + +class DistUtilsTest(object): + def setup_class(self): + self.lib_m = "m" + if sys.platform == 'win32': + #there is a small chance this fails on Mingw via environ $CC + import distutils.ccompiler + if distutils.ccompiler.get_default_compiler() == 'msvc': + self.lib_m = 'msvcrt' + + def teardown_class(self): + if udir.isdir(): + udir.remove(ignore_errors=True) + udir.ensure(dir=1) + + def test_locate_engine_class(self): + cls = _locate_engine_class(FFI(), self.generic) + if self.generic: + # asked for the generic engine, which must not generate a + # CPython extension module + assert not cls._gen_python_module + else: + # asked for the CPython engine: check that we got it, unless + # we are running on top of PyPy, where the generic engine is + # always better + if '__pypy__' not in sys.builtin_module_names: + assert cls._gen_python_module + + def test_write_source(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + v.write_source() + with open(v.sourcefilename, 'r') as f: + data = f.read() + assert csrc in data + + def test_write_source_explicit_filename(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + v.sourcefilename = filename = str(udir.join('write_source.c')) + v.write_source() + assert filename == v.sourcefilename + with open(filename, 'r') as f: + data = f.read() + assert csrc in data + + def test_write_source_to_file_obj(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + try: + from StringIO import StringIO + except ImportError: + from io import StringIO + f = StringIO() + v.write_source(file=f) + assert csrc in f.getvalue() + + def test_compile_module(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + v.compile_module() + assert v.get_module_name().startswith('_cffi_') + if v.generates_python_module(): + mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) + assert hasattr(mod, '_cffi_setup') + + def test_compile_module_explicit_filename(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!2*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + basename = self.__class__.__name__ + 'test_compile_module' + v.modulefilename = filename = str(udir.join(basename + '.so')) + v.compile_module() + assert filename == v.modulefilename + assert v.get_module_name() == basename + if v.generates_python_module(): + mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) + assert hasattr(mod, '_cffi_setup') + + def test_name_from_checksum_of_cdef(self): + names = [] + for csrc in ['double', 'double', 'float']: + ffi = FFI() + ffi.cdef("%s sin(double x);" % csrc) + v = Verifier(ffi, "#include ", + force_generic_engine=self.generic, + libraries=[self.lib_m]) + names.append(v.get_module_name()) + assert names[0] == names[1] != names[2] + + def test_name_from_checksum_of_csrc(self): + names = [] + for csrc in ['123', '123', '1234']: + ffi = FFI() + ffi.cdef("double sin(double x);") + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + names.append(v.get_module_name()) + assert names[0] == names[1] != names[2] + + def test_load_library(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!3*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + library = v.load_library() + assert library.sin(12.3) == math.sin(12.3) + + def test_verifier_args(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there %s!4*/#include "test_verifier_args.h"\n' % self + udir.join('test_verifier_args.h').write('#include \n') + v = Verifier(ffi, csrc, include_dirs=[str(udir)], + force_generic_engine=self.generic, + libraries=[self.lib_m]) + library = v.load_library() + assert library.sin(12.3) == math.sin(12.3) + + def test_verifier_object_from_ffi(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = "/*6%s*/\n#include " % self + lib = ffi.verify(csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + assert lib.sin(12.3) == math.sin(12.3) + assert isinstance(ffi.verifier, Verifier) + with open(ffi.verifier.sourcefilename, 'r') as f: + data = f.read() + assert csrc in data + + def test_extension_object(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*7%s*/' % self + ''' + #include + #ifndef TEST_EXTENSION_OBJECT + # error "define_macros missing" + #endif + ''' + lib = ffi.verify(csrc, define_macros=[('TEST_EXTENSION_OBJECT', '1')], + force_generic_engine=self.generic, + libraries=[self.lib_m]) + assert lib.sin(12.3) == math.sin(12.3) + v = ffi.verifier + ext = v.get_extension() + assert 'distutils.extension.Extension' in str(ext.__class__) or \ + 'setuptools.extension.Extension' in str(ext.__class__) + assert ext.sources == [maybe_relative_path(v.sourcefilename)] + assert ext.name == v.get_module_name() + assert ext.define_macros == [('TEST_EXTENSION_OBJECT', '1')] + + def test_extension_forces_write_source(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there9!%s*/\n#include \n' % self + v = Verifier(ffi, csrc, force_generic_engine=self.generic, + libraries=[self.lib_m]) + assert not os.path.exists(v.sourcefilename) + v.get_extension() + assert os.path.exists(v.sourcefilename) + + def test_extension_object_extra_sources(self): + ffi = FFI() + ffi.cdef("double test1eoes(double x);") + extra_source = str(udir.join('extension_extra_sources.c')) + with open(extra_source, 'w') as f: + f.write('double test1eoes(double x) { return x * 6.0; }\n') + csrc = '/*9%s*/' % self + ''' + double test1eoes(double x); /* or #include "extra_sources.h" */ + ''' + lib = ffi.verify(csrc, sources=[extra_source], + force_generic_engine=self.generic) + assert lib.test1eoes(7.0) == 42.0 + v = ffi.verifier + ext = v.get_extension() + assert 'distutils.extension.Extension' in str(ext.__class__) or \ + 'setuptools.extension.Extension' in str(ext.__class__) + assert ext.sources == [maybe_relative_path(v.sourcefilename), + extra_source] + assert ext.name == v.get_module_name() + + def test_install_and_reload_module(self, targetpackage='', ext_package=''): + KEY = repr(self) + if not hasattr(os, 'fork'): + py.test.skip("test requires os.fork()") + + if targetpackage: + udir.ensure(targetpackage, dir=1).ensure('__init__.py') + sys.path.insert(0, str(udir)) + + def make_ffi(**verifier_args): + ffi = FFI() + ffi.cdef("/* %s, %s, %s */" % (KEY, targetpackage, ext_package)) + ffi.cdef("double test1iarm(double x);") + csrc = "double test1iarm(double x) { return x * 42.0; }" + lib = ffi.verify(csrc, force_generic_engine=self.generic, + ext_package=ext_package, + **verifier_args) + return ffi, lib + + childpid = os.fork() + if childpid == 0: + # in the child + ffi, lib = make_ffi() + assert lib.test1iarm(1.5) == 63.0 + # "install" the module by moving it into udir (/targetpackage) + if targetpackage: + target = udir.join(targetpackage) + else: + target = udir + shutil.move(ffi.verifier.modulefilename, str(target)) + os._exit(0) + # in the parent + _, status = os.waitpid(childpid, 0) + if not (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0): + raise AssertionError # see error above in subprocess + + from cffi import ffiplatform + prev_compile = ffiplatform.compile + try: + if targetpackage == ext_package: + ffiplatform.compile = lambda *args: dont_call_me_any_more + # won't find it in tmpdir, but should find it correctly + # installed in udir + ffi, lib = make_ffi() + assert lib.test1iarm(0.5) == 21.0 + finally: + ffiplatform.compile = prev_compile + + def test_install_and_reload_module_package(self): + self.test_install_and_reload_module(targetpackage='foo_iarmp', + ext_package='foo_iarmp') + + def test_install_and_reload_module_ext_package_not_found(self): + self.test_install_and_reload_module(targetpackage='foo_epnf', + ext_package='not_found') + + def test_tag(self): + ffi = FFI() + ffi.cdef("/* %s test_tag */ double test1tag(double x);" % self) + csrc = "double test1tag(double x) { return x - 42.0; }" + lib = ffi.verify(csrc, force_generic_engine=self.generic, + tag='xxtest_tagxx') + assert lib.test1tag(143) == 101.0 + assert '_cffi_xxtest_tagxx_' in ffi.verifier.modulefilename + + def test_modulename(self): + ffi = FFI() + ffi.cdef("/* %s test_modulename */ double test1foo(double x);" % self) + csrc = "double test1foo(double x) { return x - 63.0; }" + modname = 'xxtest_modulenamexx%d' % (self.generic,) + lib = ffi.verify(csrc, force_generic_engine=self.generic, + modulename=modname) + assert lib.test1foo(143) == 80.0 + suffix = _get_so_suffixes()[0] + fn1 = os.path.join(ffi.verifier.tmpdir, modname + '.c') + fn2 = os.path.join(ffi.verifier.tmpdir, modname + suffix) + assert ffi.verifier.sourcefilename == fn1 + assert ffi.verifier.modulefilename == fn2 + + +class TestDistUtilsCPython(DistUtilsTest): + generic = False + +class TestDistUtilsGeneric(DistUtilsTest): + generic = True diff --git a/testing/cffi0/test_zintegration.py b/testing/cffi0/test_zintegration.py new file mode 100644 index 0000000..d56dac2 --- /dev/null +++ b/testing/cffi0/test_zintegration.py @@ -0,0 +1,176 @@ +import py, os, sys, shutil +import subprocess +from testing.udir import udir + +if sys.platform == 'win32': + py.test.skip('snippets do not run on win32') +if sys.version_info < (2, 7): + py.test.skip('fails e.g. on a Debian/Ubuntu which patches virtualenv' + ' in a non-2.6-friendly way') + +def create_venv(name): + tmpdir = udir.join(name) + try: + subprocess.check_call(['virtualenv', + #'--never-download', <= could be added, but causes failures + # in random cases on random machines + '-p', os.path.abspath(sys.executable), + str(tmpdir)]) + except OSError as e: + py.test.skip("Cannot execute virtualenv: %s" % (e,)) + + site_packages = None + for dirpath, dirnames, filenames in os.walk(str(tmpdir)): + if os.path.basename(dirpath) == 'site-packages': + site_packages = dirpath + break + paths = "" + if site_packages: + try: + from cffi import _pycparser + modules = ('cffi', '_cffi_backend') + except ImportError: + modules = ('cffi', '_cffi_backend', 'pycparser') + try: + import ply + except ImportError: + pass + else: + modules += ('ply',) # needed for older versions of pycparser + paths = [] + for module in modules: + target = __import__(module, None, None, []) + if not hasattr(target, '__file__'): # for _cffi_backend on pypy + continue + src = os.path.abspath(target.__file__) + for end in ['__init__.pyc', '__init__.pyo', '__init__.py']: + if src.lower().endswith(end): + src = src[:-len(end)-1] + break + paths.append(os.path.dirname(src)) + paths = os.pathsep.join(paths) + return tmpdir, paths + +SNIPPET_DIR = py.path.local(__file__).join('..', 'snippets') + +def really_run_setup_and_program(dirname, venv_dir_and_paths, python_snippet): + venv_dir, paths = venv_dir_and_paths + def remove(dir): + dir = str(SNIPPET_DIR.join(dirname, dir)) + shutil.rmtree(dir, ignore_errors=True) + remove('build') + remove('__pycache__') + for basedir in os.listdir(str(SNIPPET_DIR.join(dirname))): + remove(os.path.join(basedir, '__pycache__')) + olddir = os.getcwd() + python_f = udir.join('x.py') + python_f.write(py.code.Source(python_snippet)) + try: + os.chdir(str(SNIPPET_DIR.join(dirname))) + if os.name == 'nt': + bindir = 'Scripts' + else: + bindir = 'bin' + vp = str(venv_dir.join(bindir).join('python')) + env = os.environ.copy() + env['PYTHONPATH'] = paths + subprocess.check_call((vp, 'setup.py', 'clean'), env=env) + subprocess.check_call((vp, 'setup.py', 'install'), env=env) + subprocess.check_call((vp, str(python_f)), env=env) + finally: + os.chdir(olddir) + +def run_setup_and_program(dirname, python_snippet): + venv_dir = create_venv(dirname + '-cpy') + really_run_setup_and_program(dirname, venv_dir, python_snippet) + # + sys._force_generic_engine_ = True + try: + venv_dir = create_venv(dirname + '-gen') + really_run_setup_and_program(dirname, venv_dir, python_snippet) + finally: + del sys._force_generic_engine_ + # the two files lextab.py and yacctab.py are created by not-correctly- + # installed versions of pycparser. + assert not os.path.exists(str(SNIPPET_DIR.join(dirname, 'lextab.py'))) + assert not os.path.exists(str(SNIPPET_DIR.join(dirname, 'yacctab.py'))) + +class TestZIntegration(object): + def teardown_class(self): + if udir.isdir(): + udir.remove(ignore_errors=True) + udir.ensure(dir=1) + + def test_infrastructure(self): + run_setup_and_program('infrastructure', ''' + import snip_infrastructure + assert snip_infrastructure.func() == 42 + ''') + + def test_distutils_module(self): + run_setup_and_program("distutils_module", ''' + import snip_basic_verify + p = snip_basic_verify.C.getpwuid(0) + assert snip_basic_verify.ffi.string(p.pw_name) == b"root" + ''') + + def test_distutils_package_1(self): + run_setup_and_program("distutils_package_1", ''' + import snip_basic_verify1 + p = snip_basic_verify1.C.getpwuid(0) + assert snip_basic_verify1.ffi.string(p.pw_name) == b"root" + ''') + + def test_distutils_package_2(self): + run_setup_and_program("distutils_package_2", ''' + import snip_basic_verify2 + p = snip_basic_verify2.C.getpwuid(0) + assert snip_basic_verify2.ffi.string(p.pw_name) == b"root" + ''') + + def test_setuptools_module(self): + run_setup_and_program("setuptools_module", ''' + import snip_setuptools_verify + p = snip_setuptools_verify.C.getpwuid(0) + assert snip_setuptools_verify.ffi.string(p.pw_name) == b"root" + ''') + + def test_setuptools_package_1(self): + run_setup_and_program("setuptools_package_1", ''' + import snip_setuptools_verify1 + p = snip_setuptools_verify1.C.getpwuid(0) + assert snip_setuptools_verify1.ffi.string(p.pw_name) == b"root" + ''') + + def test_setuptools_package_2(self): + run_setup_and_program("setuptools_package_2", ''' + import snip_setuptools_verify2 + p = snip_setuptools_verify2.C.getpwuid(0) + assert snip_setuptools_verify2.ffi.string(p.pw_name) == b"root" + ''') + + def test_set_py_limited_api(self): + from cffi.setuptools_ext import _set_py_limited_api + try: + import setuptools + except ImportError as e: + py.test.skip(str(e)) + orig_version = setuptools.__version__ + expecting_limited_api = not hasattr(sys, 'gettotalrefcount') + try: + setuptools.__version__ = '26.0.0' + from setuptools import Extension + + kwds = _set_py_limited_api(Extension, {}) + assert kwds.get('py_limited_api', False) == expecting_limited_api + + setuptools.__version__ = '25.0' + kwds = _set_py_limited_api(Extension, {}) + assert kwds.get('py_limited_api', False) == False + + setuptools.__version__ = 'development' + kwds = _set_py_limited_api(Extension, {}) + assert kwds.get('py_limited_api', False) == expecting_limited_api + + finally: + setuptools.__version__ = orig_version diff --git a/testing/cffi1/__init__.py b/testing/cffi1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/cffi1/test_cffi_binary.py b/testing/cffi1/test_cffi_binary.py new file mode 100644 index 0000000..25953db --- /dev/null +++ b/testing/cffi1/test_cffi_binary.py @@ -0,0 +1,20 @@ +import py, sys, os +import _cffi_backend + +def test_no_unknown_exported_symbols(): + if not hasattr(_cffi_backend, '__file__'): + py.test.skip("_cffi_backend module is built-in") + if not sys.platform.startswith('linux'): + py.test.skip("linux-only") + g = os.popen("objdump -T '%s'" % _cffi_backend.__file__, 'r') + for line in g: + if not line.startswith('0'): + continue + if '*UND*' in line: + continue + name = line.split()[-1] + if name.startswith('_') or name.startswith('.'): + continue + if name not in ('init_cffi_backend', 'PyInit__cffi_backend'): + raise Exception("Unexpected exported name %r" % (name,)) + g.close() diff --git a/testing/cffi1/test_commontypes.py b/testing/cffi1/test_commontypes.py new file mode 100644 index 0000000..ea7ffde --- /dev/null +++ b/testing/cffi1/test_commontypes.py @@ -0,0 +1,34 @@ +import py, os, cffi, re +import _cffi_backend + + +def getlines(): + try: + f = open(os.path.join(os.path.dirname(cffi.__file__), + '..', 'c', 'commontypes.c')) + except IOError: + py.test.skip("cannot find ../c/commontypes.c") + lines = [line for line in f.readlines() if line.strip().startswith('EQ(')] + f.close() + return lines + +def test_alphabetical_order(): + lines = getlines() + assert lines == sorted(lines) + +def test_dependencies(): + r = re.compile(r'EQ[(]"([^"]+)",(?:\s*"([A-Z0-9_]+)\s*[*]*"[)])?') + lines = getlines() + d = {} + for line in lines: + match = r.search(line) + if match is not None: + d[match.group(1)] = match.group(2) + for value in d.values(): + if value: + assert value in d + +def test_get_common_types(): + d = {} + _cffi_backend._get_common_types(d) + assert d["bool"] == "_Bool" diff --git a/testing/cffi1/test_dlopen.py b/testing/cffi1/test_dlopen.py new file mode 100644 index 0000000..1c20550 --- /dev/null +++ b/testing/cffi1/test_dlopen.py @@ -0,0 +1,225 @@ +import py +from cffi import FFI, VerificationError, CDefError +from cffi.recompiler import make_py_source +from testing.udir import udir + + +def test_simple(): + ffi = FFI() + ffi.cdef("int close(int); static const int BB = 42; int somevar;") + target = udir.join('test_simple.py') + make_py_source(ffi, 'test_simple', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_simple', + _version = 0x2601, + _types = b'\x00\x00\x01\x0D\x00\x00\x07\x01\x00\x00\x00\x0F', + _globals = (b'\xFF\xFF\xFF\x1FBB',42,b'\x00\x00\x00\x23close',0,b'\x00\x00\x01\x21somevar',0), +) +""" + +def test_global_constant(): + ffi = FFI() + ffi.cdef("static const long BB; static const float BF = 12;") + target = udir.join('test_valid_global_constant.py') + make_py_source(ffi, 'test_valid_global_constant', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_valid_global_constant', + _version = 0x2601, + _types = b'\x00\x00\x0D\x01\x00\x00\x09\x01', + _globals = (b'\x00\x00\x01\x25BB',0,b'\x00\x00\x00\x25BF',0), +) +""" + +def test_invalid_global_constant_3(): + ffi = FFI() + e = py.test.raises(CDefError, ffi.cdef, "#define BB 12.34") + assert str(e.value).startswith( + "only supports one of the following syntax:") + +def test_invalid_dotdotdot_in_macro(): + ffi = FFI() + ffi.cdef("#define FOO ...") + target = udir.join('test_invalid_dotdotdot_in_macro.py') + e = py.test.raises(VerificationError, make_py_source, ffi, + 'test_invalid_dotdotdot_in_macro', str(target)) + assert str(e.value) == ("macro FOO: cannot use the syntax '...' in " + "'#define FOO ...' when using the ABI mode") + +def test_typename(): + ffi = FFI() + ffi.cdef("typedef int foobar_t;") + target = udir.join('test_typename.py') + make_py_source(ffi, 'test_typename', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_typename', + _version = 0x2601, + _types = b'\x00\x00\x07\x01', + _typenames = (b'\x00\x00\x00\x00foobar_t',), +) +""" + +def test_enum(): + ffi = FFI() + ffi.cdef("enum myenum_e { AA, BB, CC=-42 };") + target = udir.join('test_enum.py') + make_py_source(ffi, 'test_enum', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_enum', + _version = 0x2601, + _types = b'\x00\x00\x00\x0B', + _globals = (b'\xFF\xFF\xFF\x0BAA',0,b'\xFF\xFF\xFF\x0BBB',1,b'\xFF\xFF\xFF\x0BCC',-42), + _enums = (b'\x00\x00\x00\x00\x00\x00\x00\x15myenum_e\x00AA,BB,CC',), +) +""" + +def test_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { int a; signed char b[]; }; struct bar_s;") + target = udir.join('test_struct.py') + make_py_source(ffi, 'test_struct', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_struct', + _version = 0x2601, + _types = b'\x00\x00\x07\x01\x00\x00\x03\x01\x00\x00\x01\x07\x00\x00\x00\x09\x00\x00\x01\x09', + _struct_unions = ((b'\x00\x00\x00\x03\x00\x00\x00\x10bar_s',),(b'\x00\x00\x00\x04\x00\x00\x00\x02foo_s',b'\x00\x00\x00\x11a',b'\x00\x00\x02\x11b')), +) +""" + +def test_include(): + ffi = FFI() + ffi.cdef("#define ABC 123") + ffi.set_source('test_include', None) + target = udir.join('test_include.py') + make_py_source(ffi, 'test_include', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_include', + _version = 0x2601, + _types = b'', + _globals = (b'\xFF\xFF\xFF\x1FABC',123,), +) +""" + # + ffi2 = FFI() + ffi2.include(ffi) + target2 = udir.join('test2_include.py') + make_py_source(ffi2, 'test2_include', str(target2)) + assert target2.read() == r"""# auto-generated file +import _cffi_backend +from test_include import ffi as _ffi0 + +ffi = _cffi_backend.FFI('test2_include', + _version = 0x2601, + _types = b'', + _includes = (_ffi0,), +) +""" + +def test_negative_constant(): + ffi = FFI() + ffi.cdef("static const int BB = -42;") + target = udir.join('test_negative_constant.py') + make_py_source(ffi, 'test_negative_constant', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_negative_constant', + _version = 0x2601, + _types = b'', + _globals = (b'\xFF\xFF\xFF\x1FBB',-42,), +) +""" + +def test_struct_included(): + baseffi = FFI() + baseffi.cdef("struct foo_s { int x; };") + baseffi.set_source('test_struct_included_base', None) + # + ffi = FFI() + ffi.include(baseffi) + target = udir.join('test_struct_included.py') + make_py_source(ffi, 'test_struct_included', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend +from test_struct_included_base import ffi as _ffi0 + +ffi = _cffi_backend.FFI('test_struct_included', + _version = 0x2601, + _types = b'\x00\x00\x00\x09', + _struct_unions = ((b'\x00\x00\x00\x00\x00\x00\x00\x08foo_s',),), + _includes = (_ffi0,), +) +""" + +def test_no_cross_include(): + baseffi = FFI() + baseffi.set_source('test_no_cross_include_base', "..source..") + # + ffi = FFI() + ffi.include(baseffi) + target = udir.join('test_no_cross_include.py') + py.test.raises(VerificationError, make_py_source, + ffi, 'test_no_cross_include', str(target)) + +def test_array(): + ffi = FFI() + ffi.cdef("typedef int32_t my_array_t[42];") + target = udir.join('test_array.py') + make_py_source(ffi, 'test_array', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_array', + _version = 0x2601, + _types = b'\x00\x00\x15\x01\x00\x00\x00\x05\x00\x00\x00\x2A', + _typenames = (b'\x00\x00\x00\x01my_array_t',), +) +""" + +def test_array_overflow(): + ffi = FFI() + ffi.cdef("typedef int32_t my_array_t[3000000000];") + target = udir.join('test_array_overflow.py') + py.test.raises(OverflowError, make_py_source, + ffi, 'test_array_overflow', str(target)) + +def test_global_var(): + ffi = FFI() + ffi.cdef("int myglob;") + target = udir.join('test_global_var.py') + make_py_source(ffi, 'test_global_var', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_global_var', + _version = 0x2601, + _types = b'\x00\x00\x07\x01', + _globals = (b'\x00\x00\x00\x21myglob',0,), +) +""" + +def test_bitfield(): + ffi = FFI() + ffi.cdef("struct foo_s { int y:10; short x:5; };") + target = udir.join('test_bitfield.py') + make_py_source(ffi, 'test_bitfield', str(target)) + assert target.read() == r"""# auto-generated file +import _cffi_backend + +ffi = _cffi_backend.FFI('test_bitfield', + _version = 0x2601, + _types = b'\x00\x00\x07\x01\x00\x00\x05\x01\x00\x00\x00\x09', + _struct_unions = ((b'\x00\x00\x00\x02\x00\x00\x00\x02foo_s',b'\x00\x00\x00\x13\x00\x00\x00\x0Ay',b'\x00\x00\x01\x13\x00\x00\x00\x05x'),), +) +""" diff --git a/testing/cffi1/test_dlopen_unicode_literals.py b/testing/cffi1/test_dlopen_unicode_literals.py new file mode 100644 index 0000000..e792866 --- /dev/null +++ b/testing/cffi1/test_dlopen_unicode_literals.py @@ -0,0 +1,9 @@ +import py, os + +s = """from __future__ import unicode_literals +""" + +with open(os.path.join(os.path.dirname(__file__), 'test_dlopen.py')) as f: + s += f.read() + +exec(py.code.compile(s)) diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py new file mode 100644 index 0000000..e07d6f9 --- /dev/null +++ b/testing/cffi1/test_ffi_obj.py @@ -0,0 +1,532 @@ +import py, sys +import _cffi_backend as _cffi1_backend + + +def test_ffi_new(): + ffi = _cffi1_backend.FFI() + p = ffi.new("int *") + p[0] = -42 + assert p[0] == -42 + assert type(ffi) is ffi.__class__ is _cffi1_backend.FFI + +def test_ffi_subclass(): + class FOO(_cffi1_backend.FFI): + def __init__(self, x): + self.x = x + foo = FOO(42) + assert foo.x == 42 + p = foo.new("int *") + assert p[0] == 0 + assert type(foo) is foo.__class__ is FOO + +def test_ffi_no_argument(): + py.test.raises(TypeError, _cffi1_backend.FFI, 42) + +def test_ffi_cache_type(): + ffi = _cffi1_backend.FFI() + t1 = ffi.typeof("int **") + t2 = ffi.typeof("int *") + assert t2.item is t1.item.item + assert t2 is t1.item + assert ffi.typeof("int[][10]") is ffi.typeof("int[][10]") + assert ffi.typeof("int(*)()") is ffi.typeof("int(*)()") + +def test_ffi_type_not_immortal(): + import weakref, gc + ffi = _cffi1_backend.FFI() + t1 = ffi.typeof("int **") + t2 = ffi.typeof("int *") + w1 = weakref.ref(t1) + w2 = weakref.ref(t2) + del t1, ffi + gc.collect() + assert w1() is None + assert w2() is t2 + ffi = _cffi1_backend.FFI() + assert ffi.typeof(ffi.new("int **")[0]) is t2 + # + ffi = _cffi1_backend.FFI() + t1 = ffi.typeof("int ***") + t2 = ffi.typeof("int **") + w1 = weakref.ref(t1) + w2 = weakref.ref(t2) + del t2, ffi + gc.collect() + assert w1() is t1 + assert w2() is not None # kept alive by t1 + ffi = _cffi1_backend.FFI() + assert ffi.typeof("int * *") is t1.item + +def test_ffi_cache_type_globally(): + ffi1 = _cffi1_backend.FFI() + ffi2 = _cffi1_backend.FFI() + t1 = ffi1.typeof("int *") + t2 = ffi2.typeof("int *") + assert t1 is t2 + +def test_ffi_invalid(): + ffi = _cffi1_backend.FFI() + # array of 10 times an "int[]" is invalid + py.test.raises(ValueError, ffi.typeof, "int[10][]") + +def test_ffi_docstrings(): + # check that all methods of the FFI class have a docstring. + check_type = type(_cffi1_backend.FFI.new) + for methname in dir(_cffi1_backend.FFI): + if not methname.startswith('_'): + method = getattr(_cffi1_backend.FFI, methname) + if isinstance(method, check_type): + assert method.__doc__, "method FFI.%s() has no docstring" % ( + methname,) + +def test_ffi_NULL(): + NULL = _cffi1_backend.FFI.NULL + assert _cffi1_backend.FFI().typeof(NULL).cname == "void *" + +def test_ffi_no_attr(): + ffi = _cffi1_backend.FFI() + py.test.raises(AttributeError, "ffi.no_such_name") + py.test.raises(AttributeError, "ffi.no_such_name = 42") + py.test.raises(AttributeError, "del ffi.no_such_name") + +def test_ffi_string(): + ffi = _cffi1_backend.FFI() + p = ffi.new("char[]", init=b"foobar\x00baz") + assert ffi.string(p) == b"foobar" + assert ffi.string(cdata=p, maxlen=3) == b"foo" + +def test_ffi_errno(): + # xxx not really checking errno, just checking that we can read/write it + ffi = _cffi1_backend.FFI() + ffi.errno = 42 + assert ffi.errno == 42 + +def test_ffi_alignof(): + ffi = _cffi1_backend.FFI() + assert ffi.alignof("int") == 4 + assert ffi.alignof("int[]") == 4 + assert ffi.alignof("int[41]") == 4 + assert ffi.alignof("short[41]") == 2 + assert ffi.alignof(ffi.new("int[41]")) == 4 + assert ffi.alignof(ffi.new("int[]", 41)) == 4 + +def test_ffi_sizeof(): + ffi = _cffi1_backend.FFI() + assert ffi.sizeof("int") == 4 + py.test.raises(ffi.error, ffi.sizeof, "int[]") + assert ffi.sizeof("int[41]") == 41 * 4 + assert ffi.sizeof(ffi.new("int[41]")) == 41 * 4 + assert ffi.sizeof(ffi.new("int[]", 41)) == 41 * 4 + +def test_ffi_callback(): + ffi = _cffi1_backend.FFI() + assert ffi.callback("int(int)", lambda x: x + 42)(10) == 52 + assert ffi.callback("int(*)(int)", lambda x: x + 42)(10) == 52 + assert ffi.callback("int(int)", lambda x: x + "", -66)(10) == -66 + assert ffi.callback("int(int)", lambda x: x + "", error=-66)(10) == -66 + +def test_ffi_callback_decorator(): + ffi = _cffi1_backend.FFI() + assert ffi.callback(ffi.typeof("int(*)(int)"))(lambda x: x + 42)(10) == 52 + deco = ffi.callback("int(int)", error=-66) + assert deco(lambda x: x + "")(10) == -66 + assert deco(lambda x: x + 42)(10) == 52 + +def test_ffi_callback_onerror(): + ffi = _cffi1_backend.FFI() + seen = [] + def oops(*args): + seen.append(args) + + @ffi.callback("int(int)", onerror=oops) + def fn1(x): + return x + "" + assert fn1(10) == 0 + + @ffi.callback("int(int)", onerror=oops, error=-66) + def fn2(x): + return x + "" + assert fn2(10) == -66 + + assert len(seen) == 2 + exc, val, tb = seen[0] + assert exc is TypeError + assert isinstance(val, TypeError) + assert tb.tb_frame.f_code.co_name == "fn1" + exc, val, tb = seen[1] + assert exc is TypeError + assert isinstance(val, TypeError) + assert tb.tb_frame.f_code.co_name == "fn2" + # + py.test.raises(TypeError, ffi.callback, "int(int)", + lambda x: x, onerror=42) # <- not callable + +def test_ffi_getctype(): + ffi = _cffi1_backend.FFI() + assert ffi.getctype("int") == "int" + assert ffi.getctype("int", 'x') == "int x" + assert ffi.getctype("int*") == "int *" + assert ffi.getctype("int*", '') == "int *" + assert ffi.getctype("int*", 'x') == "int * x" + assert ffi.getctype("int", '*') == "int *" + assert ffi.getctype("int", replace_with=' * x ') == "int * x" + assert ffi.getctype(ffi.typeof("int*"), '*') == "int * *" + assert ffi.getctype("int", '[5]') == "int[5]" + assert ffi.getctype("int[5]", '[6]') == "int[6][5]" + assert ffi.getctype("int[5]", '(*)') == "int(*)[5]" + # special-case for convenience: automatically put '()' around '*' + assert ffi.getctype("int[5]", '*') == "int(*)[5]" + assert ffi.getctype("int[5]", '*foo') == "int(*foo)[5]" + assert ffi.getctype("int[5]", ' ** foo ') == "int(** foo)[5]" + +def test_addressof(): + ffi = _cffi1_backend.FFI() + a = ffi.new("int[10]") + b = ffi.addressof(a, 5) + b[2] = -123 + assert a[7] == -123 + +def test_handle(): + ffi = _cffi1_backend.FFI() + x = [2, 4, 6] + xp = ffi.new_handle(x) + assert ffi.typeof(xp) == ffi.typeof("void *") + assert ffi.from_handle(xp) is x + yp = ffi.new_handle([6, 4, 2]) + assert ffi.from_handle(yp) == [6, 4, 2] + +def test_handle_unique(): + ffi = _cffi1_backend.FFI() + assert ffi.new_handle(None) is not ffi.new_handle(None) + assert ffi.new_handle(None) != ffi.new_handle(None) + +def test_ffi_cast(): + ffi = _cffi1_backend.FFI() + assert ffi.cast("int(*)(int)", 0) == ffi.NULL + ffi.callback("int(int)") # side-effect of registering this string + py.test.raises(ffi.error, ffi.cast, "int(int)", 0) + +def test_ffi_invalid_type(): + ffi = _cffi1_backend.FFI() + e = py.test.raises(ffi.error, ffi.cast, "", 0) + assert str(e.value) == ("identifier expected\n" + "\n" + "^") + e = py.test.raises(ffi.error, ffi.cast, "struct struct", 0) + assert str(e.value) == ("struct or union name expected\n" + "struct struct\n" + " ^") + e = py.test.raises(ffi.error, ffi.cast, "struct never_heard_of_s", 0) + assert str(e.value) == ("undefined struct/union name\n" + "struct never_heard_of_s\n" + " ^") + e = py.test.raises(ffi.error, ffi.cast, "\t\n\x01\x1f~\x7f\x80\xff", 0) + marks = "?" if sys.version_info < (3,) else "??" + assert str(e.value) == ("identifier expected\n" + " ??~?%s%s\n" + " ^" % (marks, marks)) + e = py.test.raises(ffi.error, ffi.cast, "X" * 600, 0) + assert str(e.value) == ("undefined type name") + +def test_ffi_buffer(): + ffi = _cffi1_backend.FFI() + a = ffi.new("signed char[]", [5, 6, 7]) + assert ffi.buffer(a)[:] == b'\x05\x06\x07' + assert ffi.buffer(cdata=a, size=2)[:] == b'\x05\x06' + assert type(ffi.buffer(a)) is ffi.buffer + +def test_ffi_from_buffer(): + import array + ffi = _cffi1_backend.FFI() + a = array.array('H', [10000, 20000, 30000, 40000]) + c = ffi.from_buffer(a) + assert ffi.typeof(c) is ffi.typeof("char[]") + assert len(c) == 8 + ffi.cast("unsigned short *", c)[1] += 500 + assert list(a) == [10000, 20500, 30000, 40000] + py.test.raises(TypeError, ffi.from_buffer, a, True) + assert c == ffi.from_buffer("char[]", a, True) + assert c == ffi.from_buffer(a, require_writable=True) + # + c = ffi.from_buffer("unsigned short[]", a) + assert len(c) == 4 + assert c[1] == 20500 + # + c = ffi.from_buffer("unsigned short[2][2]", a) + assert len(c) == 2 + assert len(c[0]) == 2 + assert c[0][1] == 20500 + # + p = ffi.from_buffer(b"abcd") + assert p[2] == b"c" + # + assert p == ffi.from_buffer(b"abcd", require_writable=False) + py.test.raises((TypeError, BufferError), ffi.from_buffer, + "char[]", b"abcd", True) + py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", + require_writable=True) + +def test_memmove(): + ffi = _cffi1_backend.FFI() + p = ffi.new("short[]", [-1234, -2345, -3456, -4567, -5678]) + ffi.memmove(p, p + 1, 4) + assert list(p) == [-2345, -3456, -3456, -4567, -5678] + p[2] = 999 + ffi.memmove(p + 2, p, 6) + assert list(p) == [-2345, -3456, -2345, -3456, 999] + ffi.memmove(p + 4, ffi.new("char[]", b"\x71\x72"), 2) + if sys.byteorder == 'little': + assert list(p) == [-2345, -3456, -2345, -3456, 0x7271] + else: + assert list(p) == [-2345, -3456, -2345, -3456, 0x7172] + +def test_memmove_buffer(): + import array + ffi = _cffi1_backend.FFI() + a = array.array('H', [10000, 20000, 30000]) + p = ffi.new("short[]", 5) + ffi.memmove(p, a, 6) + assert list(p) == [10000, 20000, 30000, 0, 0] + ffi.memmove(p + 1, a, 6) + assert list(p) == [10000, 10000, 20000, 30000, 0] + b = array.array('h', [-1000, -2000, -3000]) + ffi.memmove(b, a, 4) + assert b.tolist() == [10000, 20000, -3000] + assert a.tolist() == [10000, 20000, 30000] + p[0] = 999 + p[1] = 998 + p[2] = 997 + p[3] = 996 + p[4] = 995 + ffi.memmove(b, p, 2) + assert b.tolist() == [999, 20000, -3000] + ffi.memmove(b, p + 2, 4) + assert b.tolist() == [997, 996, -3000] + p[2] = -p[2] + p[3] = -p[3] + ffi.memmove(b, p + 2, 6) + assert b.tolist() == [-997, -996, 995] + +def test_memmove_readonly_readwrite(): + ffi = _cffi1_backend.FFI() + p = ffi.new("signed char[]", 5) + ffi.memmove(p, b"abcde", 3) + assert list(p) == [ord("a"), ord("b"), ord("c"), 0, 0] + ffi.memmove(p, bytearray(b"ABCDE"), 2) + assert list(p) == [ord("A"), ord("B"), ord("c"), 0, 0] + py.test.raises((TypeError, BufferError), ffi.memmove, b"abcde", p, 3) + ba = bytearray(b"xxxxx") + ffi.memmove(dest=ba, src=p, n=3) + assert ba == bytearray(b"ABcxx") + +def test_ffi_types(): + CData = _cffi1_backend.FFI.CData + CType = _cffi1_backend.FFI.CType + ffi = _cffi1_backend.FFI() + assert isinstance(ffi.cast("int", 42), CData) + assert isinstance(ffi.typeof("int"), CType) + +def test_ffi_getwinerror(): + if sys.platform != "win32": + py.test.skip("for windows") + ffi = _cffi1_backend.FFI() + n = (1 << 29) + 42 + code, message = ffi.getwinerror(code=n) + assert code == n + +def test_ffi_new_allocator_1(): + ffi = _cffi1_backend.FFI() + alloc1 = ffi.new_allocator() + alloc2 = ffi.new_allocator(should_clear_after_alloc=False) + for retry in range(100): + p1 = alloc1("int[10]") + p2 = alloc2("int[10]") + combination = 0 + for i in range(10): + assert p1[i] == 0 + combination |= p2[i] + p1[i] = -42 + p2[i] = -43 + if combination != 0: + break + del p1, p2 + import gc; gc.collect() + else: + raise AssertionError("cannot seem to get an int[10] not " + "completely cleared") + +def test_ffi_new_allocator_2(): + ffi = _cffi1_backend.FFI() + 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] or + seen == [40, 40, raw2, raw1]) + assert repr(seen[2]) == "" + assert repr(seen[3]) == "" + +def test_ffi_new_allocator_3(): + ffi = _cffi1_backend.FFI() + 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(): + ffi = _cffi1_backend.FFI() + 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_bool_issue228(): + ffi = _cffi1_backend.FFI() + fntype = ffi.typeof("int(*callback)(bool is_valid)") + assert repr(fntype.args[0]) == "" + +def test_FILE_issue228(): + fntype1 = _cffi1_backend.FFI().typeof("FILE *") + fntype2 = _cffi1_backend.FFI().typeof("FILE *") + assert repr(fntype1) == "" + assert fntype1 is fntype2 + +def test_cast_from_int_type_to_bool(): + ffi = _cffi1_backend.FFI() + for basetype in ['char', 'short', 'int', 'long', 'long long']: + for sign in ['signed', 'unsigned']: + type = '%s %s' % (sign, basetype) + assert int(ffi.cast("_Bool", ffi.cast(type, 42))) == 1 + assert int(ffi.cast("bool", ffi.cast(type, 42))) == 1 + assert int(ffi.cast("_Bool", ffi.cast(type, 0))) == 0 + +def test_init_once(): + def do_init(): + seen.append(1) + return 42 + ffi = _cffi1_backend.FFI() + seen = [] + for i in range(3): + res = ffi.init_once(do_init, "tag1") + assert res == 42 + assert seen == [1] + for i in range(3): + res = ffi.init_once(do_init, "tag2") + assert res == 42 + assert seen == [1, 1] + +def test_init_once_multithread(): + if sys.version_info < (3,): + import thread + else: + import _thread as thread + import time + # + def do_init(): + print('init!') + seen.append('init!') + time.sleep(1) + seen.append('init done') + print('init done') + return 7 + ffi = _cffi1_backend.FFI() + seen = [] + for i in range(6): + def f(): + res = ffi.init_once(do_init, "tag") + seen.append(res) + thread.start_new_thread(f, ()) + time.sleep(1.5) + assert seen == ['init!', 'init done'] + 6 * [7] + +def test_init_once_failure(): + def do_init(): + seen.append(1) + raise ValueError + ffi = _cffi1_backend.FFI() + seen = [] + for i in range(5): + py.test.raises(ValueError, ffi.init_once, do_init, "tag") + assert seen == [1] * (i + 1) + +def test_init_once_multithread_failure(): + if sys.version_info < (3,): + import thread + else: + import _thread as thread + import time + def do_init(): + seen.append('init!') + time.sleep(1) + seen.append('oops') + raise ValueError + ffi = _cffi1_backend.FFI() + seen = [] + for i in range(3): + def f(): + py.test.raises(ValueError, ffi.init_once, do_init, "tag") + thread.start_new_thread(f, ()) + i = 0 + while len(seen) < 6: + i += 1 + assert i < 20 + time.sleep(0.51) + assert seen == ['init!', 'oops'] * 3 + +def test_unpack(): + ffi = _cffi1_backend.FFI() + p = ffi.new("char[]", b"abc\x00def") + assert ffi.unpack(p+1, 7) == b"bc\x00def\x00" + p = ffi.new("int[]", [-123456789]) + assert ffi.unpack(p, 1) == [-123456789] + +def test_negative_array_size(): + ffi = _cffi1_backend.FFI() + py.test.raises(ffi.error, ffi.cast, "int[-5]", 0) diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py new file mode 100644 index 0000000..209cb30 --- /dev/null +++ b/testing/cffi1/test_new_ffi_1.py @@ -0,0 +1,1793 @@ +import py +import platform, imp +import sys, os, ctypes +import cffi +from testing.udir import udir +from testing.support import * +from cffi.recompiler import recompile +from cffi.cffi_opcode import PRIMITIVE_TO_INDEX + +SIZE_OF_INT = ctypes.sizeof(ctypes.c_int) +SIZE_OF_LONG = ctypes.sizeof(ctypes.c_long) +SIZE_OF_SHORT = ctypes.sizeof(ctypes.c_short) +SIZE_OF_PTR = ctypes.sizeof(ctypes.c_void_p) +SIZE_OF_WCHAR = ctypes.sizeof(ctypes.c_wchar) + + +def setup_module(): + global ffi, construction_params + ffi1 = cffi.FFI() + DEFS = r""" + struct repr { short a, b, c; }; + struct simple { int a; short b, c; }; + struct array { int a[2]; char b[3]; }; + struct recursive { int value; struct recursive *next; }; + union simple_u { int a; short b, c; }; + union init_u { char a; int b; }; + struct four_s { int a; short b, c, d; }; + union four_u { int a; short b, c, d; }; + struct string { const char *name; }; + struct ustring { const wchar_t *name; }; + struct voidp { void *p; int *q; short *r; }; + struct ab { int a, b; }; + struct abc { int a, b, c; }; + + /* don't use A0, B0, CC0, D0 because termios.h might be included + and it has its own #defines for these names */ + enum foq { cffiA0, cffiB0, cffiCC0, cffiD0 }; + enum bar { A1, B1=-2, CC1, D1, E1 }; + enum baz { A2=0x1000, B2=0x2000 }; + enum foo2 { A3, B3, C3, D3 }; + struct bar_with_e { enum foo2 e; }; + enum noncont { A4, B4=42, C4 }; + enum etypes {A5='!', B5='\'', C5=0x10, D5=010, E5=- 0x10, F5=-010}; + typedef enum { Value0 = 0 } e_t, *pe_t; + enum e_noninj { AA3=0, BB3=0, CC3=0, DD3=0 }; + enum e_prev { AA4, BB4=2, CC4=4, DD4=BB4, EE4, FF4=CC4, GG4=FF4 }; + + struct nesting { struct abc d, e; }; + struct array2 { int a, b; int c[99]; }; + struct align { char a; short b; char c; }; + struct bitfield { int a:10, b:20, c:3; }; + typedef enum { AA2, BB2, CC2 } foo_e_t; + typedef struct { foo_e_t f:2; } bfenum_t; + typedef struct { int a; } anon_foo_t; + typedef struct { char b, c; } anon_bar_t; + typedef struct named_foo_s { int a; } named_foo_t, *named_foo_p; + typedef struct { int a; } unnamed_foo_t, *unnamed_foo_p; + struct nonpacked { char a; int b; }; + struct array0 { int len; short data[0]; }; + struct array_no_length { int x; int a[]; }; + + struct nested_anon { + struct { int a, b; }; + union { int c, d; }; + }; + struct nested_field_ofs_s { + struct { int a; char b; }; + union { char c; }; + }; + union nested_anon_u { + struct { int a, b; }; + union { int c, d; }; + }; + struct abc50 { int a, b; int c[50]; }; + struct ints_and_bitfield { int a,b,c,d,e; int x:1; }; + """ + DEFS_PACKED = """ + struct is_packed { char a; int b; } /*here*/; + """ + if sys.platform == "win32": + DEFS = DEFS.replace('data[0]', 'data[1]') # not supported + CCODE = (DEFS + "\n#pragma pack(push,1)\n" + DEFS_PACKED + + "\n#pragma pack(pop)\n") + else: + CCODE = (DEFS + + DEFS_PACKED.replace('/*here*/', '__attribute__((packed))')) + + ffi1.cdef(DEFS) + ffi1.cdef(DEFS_PACKED, packed=True) + ffi1.set_source("test_new_ffi_1", CCODE) + + outputfilename = recompile(ffi1, "test_new_ffi_1", CCODE, + tmpdir=str(udir)) + module = imp.load_dynamic("test_new_ffi_1", outputfilename) + ffi = module.ffi + construction_params = (ffi1, CCODE) + + +class TestNewFFI1: + + def test_integer_ranges(self): + for (c_type, size) in [('char', 1), + ('short', 2), + ('short int', 2), + ('', 4), + ('int', 4), + ('long', SIZE_OF_LONG), + ('long int', SIZE_OF_LONG), + ('long long', 8), + ('long long int', 8), + ]: + for unsigned in [None, False, True]: + c_decl = {None: '', + False: 'signed ', + True: 'unsigned '}[unsigned] + c_type + if c_decl == 'char' or c_decl == '': + continue + self._test_int_type(ffi, c_decl, size, unsigned) + + def test_fixedsize_int(self): + for size in [1, 2, 4, 8]: + self._test_int_type(ffi, 'int%d_t' % (8*size), size, False) + self._test_int_type(ffi, 'uint%d_t' % (8*size), size, True) + self._test_int_type(ffi, 'intptr_t', SIZE_OF_PTR, False) + self._test_int_type(ffi, 'uintptr_t', SIZE_OF_PTR, True) + self._test_int_type(ffi, 'ptrdiff_t', SIZE_OF_PTR, False) + self._test_int_type(ffi, 'size_t', SIZE_OF_PTR, True) + self._test_int_type(ffi, 'ssize_t', SIZE_OF_PTR, False) + + def _test_int_type(self, ffi, c_decl, size, unsigned): + if unsigned: + min = 0 + max = (1 << (8*size)) - 1 + else: + min = -(1 << (8*size-1)) + max = (1 << (8*size-1)) - 1 + min = int(min) + max = int(max) + p = ffi.cast(c_decl, min) + assert p == min + assert bool(p) is bool(min) + assert int(p) == min + p = ffi.cast(c_decl, max) + assert int(p) == max + p = ffi.cast(c_decl, long(max)) + assert int(p) == max + q = ffi.cast(c_decl, min - 1) + assert ffi.typeof(q) is ffi.typeof(p) and int(q) == max + q = ffi.cast(c_decl, long(min - 1)) + assert ffi.typeof(q) is ffi.typeof(p) and int(q) == max + assert q == p + assert int(q) == int(p) + assert hash(q) == hash(p) + c_decl_ptr = '%s *' % c_decl + py.test.raises(OverflowError, ffi.new, c_decl_ptr, min - 1) + py.test.raises(OverflowError, ffi.new, c_decl_ptr, max + 1) + py.test.raises(OverflowError, ffi.new, c_decl_ptr, long(min - 1)) + py.test.raises(OverflowError, ffi.new, c_decl_ptr, long(max + 1)) + assert ffi.new(c_decl_ptr, min)[0] == min + assert ffi.new(c_decl_ptr, max)[0] == max + assert ffi.new(c_decl_ptr, long(min))[0] == min + assert ffi.new(c_decl_ptr, long(max))[0] == max + + def test_new_unsupported_type(self): + e = py.test.raises(TypeError, ffi.new, "int") + assert str(e.value) == "expected a pointer or array ctype, got 'int'" + + def test_new_single_integer(self): + p = ffi.new("int *") # similar to ffi.new("int[1]") + assert p[0] == 0 + p[0] = -123 + assert p[0] == -123 + p = ffi.new("int *", -42) + assert p[0] == -42 + assert repr(p) == "" % SIZE_OF_INT + + def test_new_array_no_arg(self): + p = ffi.new("int[10]") + # the object was zero-initialized: + for i in range(10): + assert p[i] == 0 + + def test_array_indexing(self): + p = ffi.new("int[10]") + p[0] = 42 + p[9] = 43 + assert p[0] == 42 + assert p[9] == 43 + py.test.raises(IndexError, "p[10]") + py.test.raises(IndexError, "p[10] = 44") + py.test.raises(IndexError, "p[-1]") + py.test.raises(IndexError, "p[-1] = 44") + + def test_new_array_args(self): + # this tries to be closer to C: where we say "int x[5] = {10, 20, ..}" + # then here we must enclose the items in a list + p = ffi.new("int[5]", [10, 20, 30, 40, 50]) + assert p[0] == 10 + assert p[1] == 20 + assert p[2] == 30 + assert p[3] == 40 + assert p[4] == 50 + p = ffi.new("int[4]", [25]) + assert p[0] == 25 + assert p[1] == 0 # follow C convention rather than LuaJIT's + assert p[2] == 0 + assert p[3] == 0 + p = ffi.new("int[4]", [ffi.cast("int", -5)]) + assert p[0] == -5 + assert repr(p) == "" % (4*SIZE_OF_INT) + + def test_new_array_varsize(self): + p = ffi.new("int[]", 10) # a single integer is the length + assert p[9] == 0 + py.test.raises(IndexError, "p[10]") + # + py.test.raises(TypeError, ffi.new, "int[]") + # + p = ffi.new("int[]", [-6, -7]) # a list is all the items, like C + assert p[0] == -6 + assert p[1] == -7 + py.test.raises(IndexError, "p[2]") + assert repr(p) == "" % (2*SIZE_OF_INT) + # + p = ffi.new("int[]", 0) + py.test.raises(IndexError, "p[0]") + py.test.raises(ValueError, ffi.new, "int[]", -1) + assert repr(p) == "" + + def test_pointer_init(self): + n = ffi.new("int *", 24) + a = ffi.new("int *[10]", [ffi.NULL, ffi.NULL, n, n, ffi.NULL]) + for i in range(10): + if i not in (2, 3): + assert a[i] == ffi.NULL + assert a[2] == a[3] == n + + def test_cannot_cast(self): + a = ffi.new("short int[10]") + e = py.test.raises(TypeError, ffi.new, "long int **", a) + msg = str(e.value) + assert "'short[10]'" in msg and "'long *'" in msg + + def test_new_pointer_to_array(self): + a = ffi.new("int[4]", [100, 102, 104, 106]) + p = ffi.new("int **", a) + assert p[0] == ffi.cast("int *", a) + assert p[0][2] == 104 + p = ffi.cast("int *", a) + assert p[0] == 100 + assert p[1] == 102 + assert p[2] == 104 + assert p[3] == 106 + # keepalive: a + + def test_pointer_direct(self): + p = ffi.cast("int*", 0) + assert p is not None + assert bool(p) is False + assert p == ffi.cast("int*", 0) + assert p != None + assert repr(p) == "" + a = ffi.new("int[]", [123, 456]) + p = ffi.cast("int*", a) + assert bool(p) is True + assert p == ffi.cast("int*", a) + assert p != ffi.cast("int*", 0) + assert p[0] == 123 + assert p[1] == 456 + + def test_repr(self): + typerepr = "" + p = ffi.cast("short unsigned int", 0) + assert repr(p) == "" + assert repr(ffi.typeof(p)) == typerepr % "unsigned short" + p = ffi.cast("unsigned short int", 0) + assert repr(p) == "" + assert repr(ffi.typeof(p)) == typerepr % "unsigned short" + p = ffi.cast("int*", 0) + assert repr(p) == "" + assert repr(ffi.typeof(p)) == typerepr % "int *" + # + p = ffi.new("int*") + assert repr(p) == "" % SIZE_OF_INT + assert repr(ffi.typeof(p)) == typerepr % "int *" + p = ffi.new("int**") + assert repr(p) == "" % SIZE_OF_PTR + assert repr(ffi.typeof(p)) == typerepr % "int * *" + p = ffi.new("int [2]") + assert repr(p) == "" % (2*SIZE_OF_INT) + assert repr(ffi.typeof(p)) == typerepr % "int[2]" + p = ffi.new("int*[2][3]") + assert repr(p) == "" % ( + 6*SIZE_OF_PTR) + assert repr(ffi.typeof(p)) == typerepr % "int *[2][3]" + p = ffi.new("struct repr *") + assert repr(p) == "" % ( + 3*SIZE_OF_SHORT) + assert repr(ffi.typeof(p)) == typerepr % "struct repr *" + # + q = ffi.cast("short", -123) + assert repr(q) == "" + assert repr(ffi.typeof(q)) == typerepr % "short" + p = ffi.new("int*") + q = ffi.cast("short*", p) + assert repr(q).startswith(" 2: + assert ffi.new("wchar_t*", u+'\U00012345')[0] == u+'\U00012345' + else: + py.test.raises(TypeError, ffi.new, "wchar_t*", u+'\U00012345') + assert ffi.new("wchar_t*")[0] == u+'\x00' + assert int(ffi.cast("wchar_t", 300)) == 300 + assert not bool(ffi.cast("wchar_t", 0)) + assert bool(ffi.cast("wchar_t", 1)) + assert bool(ffi.cast("wchar_t", 65535)) + if SIZE_OF_WCHAR > 2: + assert bool(ffi.cast("wchar_t", 65536)) + py.test.raises(TypeError, ffi.new, "wchar_t*", 32) + py.test.raises(TypeError, ffi.new, "wchar_t*", "foo") + # + p = ffi.new("wchar_t[]", [u+'a', u+'b', u+'\u1234']) + assert len(p) == 3 + assert p[0] == u+'a' + assert p[1] == u+'b' and type(p[1]) is unicode + assert p[2] == u+'\u1234' + p[0] = u+'x' + assert p[0] == u+'x' and type(p[0]) is unicode + p[1] = u+'\u1357' + assert p[1] == u+'\u1357' + p = ffi.new("wchar_t[]", u+"abcd") + assert len(p) == 5 + assert p[4] == u+'\x00' + p = ffi.new("wchar_t[]", u+"a\u1234b") + assert len(p) == 4 + assert p[1] == u+'\u1234' + # + p = ffi.new("wchar_t[]", u+'\U00023456') + if SIZE_OF_WCHAR == 2: + assert len(p) == 3 + assert p[0] == u+'\ud84d' + assert p[1] == u+'\udc56' + assert p[2] == u+'\x00' + else: + assert len(p) == 2 + assert p[0] == u+'\U00023456' + assert p[1] == u+'\x00' + # + p = ffi.new("wchar_t[4]", u+"ab") + assert len(p) == 4 + assert [p[i] for i in range(4)] == [u+'a', u+'b', u+'\x00', u+'\x00'] + p = ffi.new("wchar_t[2]", u+"ab") + assert len(p) == 2 + assert [p[i] for i in range(2)] == [u+'a', u+'b'] + py.test.raises(IndexError, ffi.new, "wchar_t[2]", u+"abc") + + def test_none_as_null_doesnt_work(self): + p = ffi.new("int*[1]") + assert p[0] is not None + assert p[0] != None + assert p[0] == ffi.NULL + assert repr(p[0]) == "" + # + n = ffi.new("int*", 99) + p = ffi.new("int*[]", [n]) + assert p[0][0] == 99 + py.test.raises(TypeError, "p[0] = None") + p[0] = ffi.NULL + assert p[0] == ffi.NULL + + def test_float(self): + p = ffi.new("float[]", [-2, -2.5]) + assert p[0] == -2.0 + assert p[1] == -2.5 + p[1] += 17.75 + assert p[1] == 15.25 + # + p = ffi.new("float*", 15.75) + assert p[0] == 15.75 + py.test.raises(TypeError, int, p) + py.test.raises(TypeError, float, p) + p[0] = 0.0 + assert bool(p) is True + # + p = ffi.new("float*", 1.1) + f = p[0] + assert f != 1.1 # because of rounding effect + assert abs(f - 1.1) < 1E-7 + # + INF = 1E200 * 1E200 + assert 1E200 != INF + p[0] = 1E200 + assert p[0] == INF # infinite, not enough precision + + def test_struct_simple(self): + s = ffi.new("struct simple*") + assert s.a == s.b == s.c == 0 + s.b = -23 + assert s.b == -23 + py.test.raises(OverflowError, "s.b = 32768") + # + s = ffi.new("struct simple*", [-2, -3]) + assert s.a == -2 + assert s.b == -3 + assert s.c == 0 + py.test.raises((AttributeError, TypeError), "del s.a") + assert repr(s) == "" % ( + SIZE_OF_INT + 2 * SIZE_OF_SHORT) + # + py.test.raises(ValueError, ffi.new, "struct simple*", [1, 2, 3, 4]) + + def test_constructor_struct_from_dict(self): + s = ffi.new("struct simple*", {'b': 123, 'c': 456}) + assert s.a == 0 + assert s.b == 123 + assert s.c == 456 + py.test.raises(KeyError, ffi.new, "struct simple*", {'d': 456}) + + def test_struct_pointer(self): + s = ffi.new("struct simple*") + assert s[0].a == s[0].b == s[0].c == 0 + s[0].b = -23 + assert s[0].b == s.b == -23 + py.test.raises(OverflowError, "s[0].b = -32769") + py.test.raises(IndexError, "s[1]") + + def test_struct_opaque(self): + py.test.raises(ffi.error, ffi.new, "struct baz*") + # should 'ffi.new("struct baz **") work? it used to, but it was + # not particularly useful... + py.test.raises(ffi.error, ffi.new, "struct baz**") + + def test_pointer_to_struct(self): + s = ffi.new("struct simple *") + s.a = -42 + assert s[0].a == -42 + p = ffi.new("struct simple **", s) + assert p[0].a == -42 + assert p[0][0].a == -42 + p[0].a = -43 + assert s.a == -43 + assert s[0].a == -43 + p[0][0].a = -44 + assert s.a == -44 + assert s[0].a == -44 + s.a = -45 + assert p[0].a == -45 + assert p[0][0].a == -45 + s[0].a = -46 + assert p[0].a == -46 + assert p[0][0].a == -46 + + def test_constructor_struct_of_array(self): + s = ffi.new("struct array *", [[10, 11], [b'a', b'b', b'c']]) + assert s.a[1] == 11 + assert s.b[2] == b'c' + s.b[1] = b'X' + assert s.b[0] == b'a' + assert s.b[1] == b'X' + assert s.b[2] == b'c' + + def test_recursive_struct(self): + s = ffi.new("struct recursive*") + t = ffi.new("struct recursive*") + s.value = 123 + s.next = t + t.value = 456 + assert s.value == 123 + assert s.next.value == 456 + + def test_union_simple(self): + u = ffi.new("union simple_u*") + assert u.a == u.b == u.c == 0 + u.b = -23 + assert u.b == -23 + assert u.a != 0 + py.test.raises(OverflowError, "u.b = 32768") + # + u = ffi.new("union simple_u*", [-2]) + assert u.a == -2 + py.test.raises((AttributeError, TypeError), "del u.a") + assert repr(u) == "" % ( + SIZE_OF_INT,) + + def test_union_opaque(self): + py.test.raises(ffi.error, ffi.new, "union baz*") + # should 'ffi.new("union baz **") work? it used to, but it was + # not particularly useful... + py.test.raises(ffi.error, ffi.new, "union baz**") + + def test_union_initializer(self): + py.test.raises(TypeError, ffi.new, "union init_u*", b'A') + py.test.raises(TypeError, ffi.new, "union init_u*", 5) + py.test.raises(ValueError, ffi.new, "union init_u*", [b'A', 5]) + u = ffi.new("union init_u*", [b'A']) + assert u.a == b'A' + py.test.raises(TypeError, ffi.new, "union init_u*", [1005]) + u = ffi.new("union init_u*", {'b': 12345}) + assert u.b == 12345 + u = ffi.new("union init_u*", []) + assert u.a == b'\x00' + assert u.b == 0 + + def test_sizeof_type(self): + for c_type, expected_size in [ + ('char', 1), + ('unsigned int', 4), + ('char *', SIZE_OF_PTR), + ('int[5]', 20), + ('struct four_s', 12), + ('union four_u', 4), + ]: + size = ffi.sizeof(c_type) + assert size == expected_size, (size, expected_size, ctype) + + def test_sizeof_cdata(self): + assert ffi.sizeof(ffi.new("short*")) == SIZE_OF_PTR + assert ffi.sizeof(ffi.cast("short", 123)) == SIZE_OF_SHORT + # + a = ffi.new("int[]", [10, 11, 12, 13, 14]) + assert len(a) == 5 + assert ffi.sizeof(a) == 5 * SIZE_OF_INT + + def test_string_from_char_pointer(self): + x = ffi.new("char*", b"x") + assert str(x) == repr(x) + assert ffi.string(x) == b"x" + assert ffi.string(ffi.new("char*", b"\x00")) == b"" + py.test.raises(TypeError, ffi.new, "char*", unicode("foo")) + + def test_unicode_from_wchar_pointer(self): + self.check_wchar_t(ffi) + x = ffi.new("wchar_t*", u+"x") + assert unicode(x) == unicode(repr(x)) + assert ffi.string(x) == u+"x" + assert ffi.string(ffi.new("wchar_t*", u+"\x00")) == u+"" + + def test_string_from_char_array(self): + p = ffi.new("char[]", b"hello.") + p[5] = b'!' + assert ffi.string(p) == b"hello!" + p[6] = b'?' + assert ffi.string(p) == b"hello!?" + p[3] = b'\x00' + assert ffi.string(p) == b"hel" + assert ffi.string(p, 2) == b"he" + py.test.raises(IndexError, "p[7] = b'X'") + # + a = ffi.new("char[]", b"hello\x00world") + assert len(a) == 12 + p = ffi.cast("char *", a) + assert ffi.string(p) == b'hello' + + def test_string_from_wchar_array(self): + self.check_wchar_t(ffi) + assert ffi.string(ffi.cast("wchar_t", "x")) == u+"x" + assert ffi.string(ffi.cast("wchar_t", u+"x")) == u+"x" + x = ffi.cast("wchar_t", "x") + assert str(x) == repr(x) + assert ffi.string(x) == u+"x" + # + p = ffi.new("wchar_t[]", u+"hello.") + p[5] = u+'!' + assert ffi.string(p) == u+"hello!" + p[6] = u+'\u04d2' + assert ffi.string(p) == u+"hello!\u04d2" + p[3] = u+'\x00' + assert ffi.string(p) == u+"hel" + assert ffi.string(p, 123) == u+"hel" + py.test.raises(IndexError, "p[7] = u+'X'") + # + a = ffi.new("wchar_t[]", u+"hello\x00world") + assert len(a) == 12 + p = ffi.cast("wchar_t *", a) + assert ffi.string(p) == u+'hello' + assert ffi.string(p, 123) == u+'hello' + assert ffi.string(p, 5) == u+'hello' + assert ffi.string(p, 2) == u+'he' + + def test_fetch_const_char_p_field(self): + # 'const' is ignored so far, in the declaration of 'struct string' + t = ffi.new("const char[]", b"testing") + s = ffi.new("struct string*", [t]) + assert type(s.name) not in (bytes, str, unicode) + assert ffi.string(s.name) == b"testing" + py.test.raises(TypeError, "s.name = None") + s.name = ffi.NULL + assert s.name == ffi.NULL + + def test_fetch_const_wchar_p_field(self): + # 'const' is ignored so far + self.check_wchar_t(ffi) + t = ffi.new("const wchar_t[]", u+"testing") + s = ffi.new("struct ustring*", [t]) + assert type(s.name) not in (bytes, str, unicode) + assert ffi.string(s.name) == u+"testing" + s.name = ffi.NULL + assert s.name == ffi.NULL + + def test_voidp(self): + py.test.raises(TypeError, ffi.new, "void*") + p = ffi.new("void **") + assert p[0] == ffi.NULL + a = ffi.new("int[]", [10, 11, 12]) + p = ffi.new("void **", a) + vp = p[0] + py.test.raises(TypeError, "vp[0]") + py.test.raises(TypeError, ffi.new, "short **", a) + # + s = ffi.new("struct voidp *") + s.p = a # works + s.q = a # works + py.test.raises(TypeError, "s.r = a") # fails + b = ffi.cast("int *", a) + s.p = b # works + s.q = b # works + py.test.raises(TypeError, "s.r = b") # fails + + def test_functionptr_simple(self): + py.test.raises(TypeError, ffi.callback, "int(*)(int)", 0) + def cb(n): + return n + 1 + cb.__qualname__ = 'cb' + p = ffi.callback("int(*)(int)", cb) + res = p(41) # calling an 'int(*)(int)', i.e. a function pointer + assert res == 42 and type(res) is int + res = p(ffi.cast("int", -41)) + assert res == -40 and type(res) is int + assert repr(p).startswith( + "" % ( + SIZE_OF_PTR) + py.test.raises(TypeError, "q(43)") + res = q[0](43) + assert res == 44 + q = ffi.cast("int(*)(int)", p) + assert repr(q).startswith("" % "int(*(*)(int))(int)" + + def test_functionptr_voidptr_return(self): + def cb(): + return ffi.NULL + p = ffi.callback("void*(*)()", cb) + res = p() + assert res is not None + assert res == ffi.NULL + int_ptr = ffi.new('int*') + void_ptr = ffi.cast('void*', int_ptr) + def cb(): + return void_ptr + p = ffi.callback("void*(*)()", cb) + res = p() + assert res == void_ptr + + def test_functionptr_intptr_return(self): + def cb(): + return ffi.NULL + p = ffi.callback("int*(*)()", cb) + res = p() + assert res == ffi.NULL + int_ptr = ffi.new('int*') + def cb(): + return int_ptr + p = ffi.callback("int*(*)()", cb) + res = p() + assert repr(res).startswith("" + assert repr(ffi.cast("enum foq", -1)) == ( # enums are unsigned, if + "") or ( # they contain no neg value + sys.platform == "win32") # (but not on msvc) + # enum baz { A2=0x1000, B2=0x2000 }; + assert ffi.string(ffi.cast("enum baz", 0x1000)) == "A2" + assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B2" + + def test_enum_in_struct(self): + # enum foo2 { A3, B3, C3, D3 }; + # struct bar_with_e { enum foo2 e; }; + s = ffi.new("struct bar_with_e *") + s.e = 0 + assert s.e == 0 + s.e = 3 + assert s.e == 3 + assert s[0].e == 3 + s[0].e = 2 + assert s.e == 2 + assert s[0].e == 2 + s.e = ffi.cast("enum foo2", -1) + assert s.e in (4294967295, -1) # two choices + assert s[0].e in (4294967295, -1) + s.e = s.e + py.test.raises(TypeError, "s.e = 'B3'") + py.test.raises(TypeError, "s.e = '2'") + py.test.raises(TypeError, "s.e = '#2'") + py.test.raises(TypeError, "s.e = '#7'") + + def test_enum_non_contiguous(self): + # enum noncont { A4, B4=42, C4 }; + assert ffi.string(ffi.cast("enum noncont", 0)) == "A4" + assert ffi.string(ffi.cast("enum noncont", 42)) == "B4" + assert ffi.string(ffi.cast("enum noncont", 43)) == "C4" + invalid_value = ffi.cast("enum noncont", 2) + assert int(invalid_value) == 2 + assert ffi.string(invalid_value) == "2" + + def test_enum_char_hex_oct(self): + # enum etypes {A5='!', B5='\'', C5=0x10, D5=010, E5=- 0x10, F5=-010}; + assert ffi.string(ffi.cast("enum etypes", ord('!'))) == "A5" + assert ffi.string(ffi.cast("enum etypes", ord("'"))) == "B5" + assert ffi.string(ffi.cast("enum etypes", 16)) == "C5" + assert ffi.string(ffi.cast("enum etypes", 8)) == "D5" + assert ffi.string(ffi.cast("enum etypes", -16)) == "E5" + assert ffi.string(ffi.cast("enum etypes", -8)) == "F5" + + def test_array_of_struct(self): + s = ffi.new("struct ab[1]") + py.test.raises(AttributeError, 's.b') + py.test.raises(AttributeError, 's.b = 412') + s[0].b = 412 + assert s[0].b == 412 + py.test.raises(IndexError, 's[1]') + + def test_pointer_to_array(self): + p = ffi.new("int(**)[5]") + assert repr(p) == "" % SIZE_OF_PTR + + def test_iterate_array(self): + a = ffi.new("char[]", b"hello") + assert list(a) == [b"h", b"e", b"l", b"l", b"o", b"\0"] + assert list(iter(a)) == [b"h", b"e", b"l", b"l", b"o", b"\0"] + # + py.test.raises(TypeError, iter, ffi.cast("char *", a)) + py.test.raises(TypeError, list, ffi.cast("char *", a)) + py.test.raises(TypeError, iter, ffi.new("int *")) + py.test.raises(TypeError, list, ffi.new("int *")) + + def test_offsetof(self): + # struct abc { int a, b, c; }; + assert ffi.offsetof("struct abc", "a") == 0 + assert ffi.offsetof("struct abc", "b") == 4 + assert ffi.offsetof("struct abc", "c") == 8 + + def test_offsetof_nested(self): + # struct nesting { struct abc d, e; }; + assert ffi.offsetof("struct nesting", "e") == 12 + py.test.raises(KeyError, ffi.offsetof, "struct nesting", "e.a") + assert ffi.offsetof("struct nesting", "e", "a") == 12 + assert ffi.offsetof("struct nesting", "e", "b") == 16 + assert ffi.offsetof("struct nesting", "e", "c") == 20 + + def test_offsetof_array(self): + assert ffi.offsetof("int[]", 51) == 51 * ffi.sizeof("int") + assert ffi.offsetof("int *", 51) == 51 * ffi.sizeof("int") + # struct array2 { int a, b; int c[99]; }; + assert ffi.offsetof("struct array2", "c") == 2 * ffi.sizeof("int") + assert ffi.offsetof("struct array2", "c", 0) == 2 * ffi.sizeof("int") + assert ffi.offsetof("struct array2", "c", 51) == 53 * ffi.sizeof("int") + + def test_alignof(self): + # struct align { char a; short b; char c; }; + assert ffi.alignof("int") == 4 + assert ffi.alignof("double") in (4, 8) + assert ffi.alignof("struct align") == 2 + + def test_bitfield(self): + # struct bitfield { int a:10, b:20, c:3; }; + assert ffi.sizeof("struct bitfield") == 8 + s = ffi.new("struct bitfield *") + s.a = 511 + py.test.raises(OverflowError, "s.a = 512") + py.test.raises(OverflowError, "s[0].a = 512") + assert s.a == 511 + s.a = -512 + py.test.raises(OverflowError, "s.a = -513") + py.test.raises(OverflowError, "s[0].a = -513") + assert s.a == -512 + s.c = 3 + assert s.c == 3 + py.test.raises(OverflowError, "s.c = 4") + py.test.raises(OverflowError, "s[0].c = 4") + s.c = -4 + assert s.c == -4 + + def test_bitfield_enum(self): + # typedef enum { AA1, BB1, CC1 } foo_e_t; + # typedef struct { foo_e_t f:2; } bfenum_t; + if sys.platform == "win32": + py.test.skip("enums are not unsigned") + s = ffi.new("bfenum_t *") + s.f = 2 + assert s.f == 2 + + def test_anonymous_struct(self): + # typedef struct { int a; } anon_foo_t; + # typedef struct { char b, c; } anon_bar_t; + f = ffi.new("anon_foo_t *", [12345]) + b = ffi.new("anon_bar_t *", [b"B", b"C"]) + assert f.a == 12345 + assert b.b == b"B" + assert b.c == b"C" + assert repr(b).startswith(" s) is False + assert (p >= s) is True + assert (s < p) is False + assert (s <= p) is True + assert (s == p) is True + assert (s != p) is False + assert (s > p) is False + assert (s >= p) is True + q = p + 1 + assert (q < s) is False + assert (q <= s) is False + assert (q == s) is False + assert (q != s) is True + assert (q > s) is True + assert (q >= s) is True + assert (s < q) is True + assert (s <= q) is True + assert (s == q) is False + assert (s != q) is True + assert (s > q) is False + assert (s >= q) is False + assert (q < p) is False + assert (q <= p) is False + assert (q == p) is False + assert (q != p) is True + assert (q > p) is True + assert (q >= p) is True + assert (p < q) is True + assert (p <= q) is True + assert (p == q) is False + assert (p != q) is True + assert (p > q) is False + assert (p >= q) is False + # + assert (None == s) is False + assert (None != s) is True + assert (s == None) is False + assert (s != None) is True + assert (None == q) is False + assert (None != q) is True + assert (q == None) is False + assert (q != None) is True + + def test_integer_comparison(self): + x = ffi.cast("int", 123) + y = ffi.cast("int", 456) + assert x < y + # + z = ffi.cast("double", 78.9) + assert x > z + assert y > z + + def test_ffi_buffer_ptr(self): + a = ffi.new("short *", 100) + try: + b = ffi.buffer(a) + except NotImplementedError as e: + py.test.skip(str(e)) + content = b[:] + assert len(content) == len(b) == 2 + if sys.byteorder == 'little': + assert content == b'\x64\x00' + assert b[0] == b'\x64' + b[0] = b'\x65' + else: + assert content == b'\x00\x64' + assert b[1] == b'\x64' + b[1] = b'\x65' + assert a[0] == 101 + + def test_ffi_buffer_array(self): + a = ffi.new("int[]", list(range(100, 110))) + try: + b = ffi.buffer(a) + except NotImplementedError as e: + py.test.skip(str(e)) + content = b[:] + if sys.byteorder == 'little': + assert content.startswith(b'\x64\x00\x00\x00\x65\x00\x00\x00') + b[4] = b'\x45' + else: + assert content.startswith(b'\x00\x00\x00\x64\x00\x00\x00\x65') + b[7] = b'\x45' + assert len(content) == 4 * 10 + assert a[1] == 0x45 + + def test_ffi_buffer_ptr_size(self): + a = ffi.new("short *", 0x4243) + try: + b = ffi.buffer(a, 1) + except NotImplementedError as e: + py.test.skip(str(e)) + content = b[:] + assert len(content) == 1 + if sys.byteorder == 'little': + assert content == b'\x43' + b[0] = b'\x62' + assert a[0] == 0x4262 + else: + assert content == b'\x42' + b[0] = b'\x63' + assert a[0] == 0x6343 + + def test_ffi_buffer_array_size(self): + a1 = ffi.new("int[]", list(range(100, 110))) + a2 = ffi.new("int[]", list(range(100, 115))) + try: + ffi.buffer(a1) + except NotImplementedError as e: + py.test.skip(str(e)) + assert ffi.buffer(a1)[:] == ffi.buffer(a2, 4*10)[:] + + def test_ffi_buffer_with_file(self): + import tempfile, os, array + fd, filename = tempfile.mkstemp() + f = os.fdopen(fd, 'r+b') + a = ffi.new("int[]", list(range(1005))) + try: + ffi.buffer(a, 512) + except NotImplementedError as e: + py.test.skip(str(e)) + f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) + f.seek(0) + assert f.read() == array.array('i', range(1000)).tostring() + f.seek(0) + b = ffi.new("int[]", 1005) + f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) + assert list(a)[:1000] + [0] * (len(a)-1000) == list(b) + f.close() + os.unlink(filename) + + def test_ffi_buffer_with_io(self): + import io, array + f = io.BytesIO() + a = ffi.new("int[]", list(range(1005))) + try: + ffi.buffer(a, 512) + except NotImplementedError as e: + py.test.skip(str(e)) + f.write(ffi.buffer(a, 1000 * ffi.sizeof("int"))) + f.seek(0) + assert f.read() == array.array('i', range(1000)).tostring() + f.seek(0) + b = ffi.new("int[]", 1005) + f.readinto(ffi.buffer(b, 1000 * ffi.sizeof("int"))) + assert list(a)[:1000] + [0] * (len(a)-1000) == list(b) + f.close() + + def test_array_in_struct(self): + # struct array { int a[2]; char b[3]; }; + p = ffi.new("struct array *") + p.a[1] = 5 + assert p.a[1] == 5 + assert repr(p.a).startswith("= 4.0 + py.test.skip("not available on pypy", allow_module_level=True) + except TypeError: + # older pytest + py.test.skip("not available on pypy") + +cffi_dir = os.path.dirname(cffi_opcode.__file__) + +r_macro = re.compile(r"#define \w+[(][^\n]*|#include [^\n]*") +r_define = re.compile(r"(#define \w+) [^\n]*") +r_ifdefs = re.compile(r"(#ifdef |#endif)[^\n]*") +header = open(os.path.join(cffi_dir, 'parse_c_type.h')).read() +header = r_macro.sub(r"", header) +header = r_define.sub(r"\1 ...", header) +header = r_ifdefs.sub(r"", header) + +ffi = cffi.FFI() +ffi.cdef(header) + +lib = ffi.verify( + open(os.path.join(cffi_dir, '..', 'c', 'parse_c_type.c')).read() + """ +static const char *get_common_type(const char *search, size_t search_len) { + return NULL; +} +""", include_dirs=[cffi_dir]) + +class ParseError(Exception): + pass + +struct_names = ["bar_s", "foo", "foo_", "foo_s", "foo_s1", "foo_s12"] +assert struct_names == sorted(struct_names) + +enum_names = ["ebar_s", "efoo", "efoo_", "efoo_s", "efoo_s1", "efoo_s12"] +assert enum_names == sorted(enum_names) + +identifier_names = ["id", "id0", "id05", "id05b", "tail"] +assert identifier_names == sorted(identifier_names) + +global_names = ["FIVE", "NEG", "ZERO"] +assert global_names == sorted(global_names) + +ctx = ffi.new("struct _cffi_type_context_s *") +c_struct_names = [ffi.new("char[]", _n.encode('ascii')) for _n in struct_names] +ctx_structs = ffi.new("struct _cffi_struct_union_s[]", len(struct_names)) +for _i in range(len(struct_names)): + ctx_structs[_i].name = c_struct_names[_i] +ctx_structs[3].flags = lib._CFFI_F_UNION +ctx.struct_unions = ctx_structs +ctx.num_struct_unions = len(struct_names) + +c_enum_names = [ffi.new("char[]", _n.encode('ascii')) for _n in enum_names] +ctx_enums = ffi.new("struct _cffi_enum_s[]", len(enum_names)) +for _i in range(len(enum_names)): + ctx_enums[_i].name = c_enum_names[_i] +ctx.enums = ctx_enums +ctx.num_enums = len(enum_names) + +c_identifier_names = [ffi.new("char[]", _n.encode('ascii')) + for _n in identifier_names] +ctx_identifiers = ffi.new("struct _cffi_typename_s[]", len(identifier_names)) +for _i in range(len(identifier_names)): + ctx_identifiers[_i].name = c_identifier_names[_i] + ctx_identifiers[_i].type_index = 100 + _i +ctx.typenames = ctx_identifiers +ctx.num_typenames = len(identifier_names) + +@ffi.callback("int(unsigned long long *)") +def fetch_constant_five(p): + p[0] = 5 + return 0 +@ffi.callback("int(unsigned long long *)") +def fetch_constant_zero(p): + p[0] = 0 + return 1 +@ffi.callback("int(unsigned long long *)") +def fetch_constant_neg(p): + p[0] = 123321 + return 1 + +ctx_globals = ffi.new("struct _cffi_global_s[]", len(global_names)) +c_glob_names = [ffi.new("char[]", _n.encode('ascii')) for _n in global_names] +for _i, _fn in enumerate([fetch_constant_five, + fetch_constant_neg, + fetch_constant_zero]): + ctx_globals[_i].name = c_glob_names[_i] + ctx_globals[_i].address = _fn + ctx_globals[_i].type_op = ffi.cast("_cffi_opcode_t", + cffi_opcode.OP_CONSTANT_INT if _i != 1 + else cffi_opcode.OP_ENUM) +ctx.globals = ctx_globals +ctx.num_globals = len(global_names) + + +def parse(input): + out = ffi.new("_cffi_opcode_t[]", 100) + info = ffi.new("struct _cffi_parse_info_s *") + info.ctx = ctx + info.output = out + info.output_size = len(out) + for j in range(len(out)): + out[j] = ffi.cast("void *", -424242) + res = lib.parse_c_type(info, input.encode('ascii')) + if res < 0: + raise ParseError(ffi.string(info.error_message).decode('ascii'), + info.error_location) + assert 0 <= res < len(out) + result = [] + for j in range(len(out)): + if out[j] == ffi.cast("void *", -424242): + assert res < j + break + i = int(ffi.cast("intptr_t", out[j])) + if j == res: + result.append('->') + result.append(i) + return result + +def parsex(input): + result = parse(input) + def str_if_int(x): + if isinstance(x, str): + return x + return '%d,%d' % (x & 255, x >> 8) + return ' '.join(map(str_if_int, result)) + +def parse_error(input, expected_msg, expected_location): + e = py.test.raises(ParseError, parse, input) + assert e.value.args[0] == expected_msg + assert e.value.args[1] == expected_location + +def make_getter(name): + opcode = getattr(lib, '_CFFI_OP_' + name) + def getter(value): + return opcode | (value << 8) + return getter + +Prim = make_getter('PRIMITIVE') +Pointer = make_getter('POINTER') +Array = make_getter('ARRAY') +OpenArray = make_getter('OPEN_ARRAY') +NoOp = make_getter('NOOP') +Func = make_getter('FUNCTION') +FuncEnd = make_getter('FUNCTION_END') +Struct = make_getter('STRUCT_UNION') +Enum = make_getter('ENUM') +Typename = make_getter('TYPENAME') + + +def test_simple(): + for simple_type, expected in [ + ("int", lib._CFFI_PRIM_INT), + ("signed int", lib._CFFI_PRIM_INT), + (" long ", lib._CFFI_PRIM_LONG), + ("long int", lib._CFFI_PRIM_LONG), + ("unsigned short", lib._CFFI_PRIM_USHORT), + ("long double", lib._CFFI_PRIM_LONGDOUBLE), + (" float _Complex", lib._CFFI_PRIM_FLOATCOMPLEX), + ("double _Complex ", lib._CFFI_PRIM_DOUBLECOMPLEX), + ]: + assert parse(simple_type) == ['->', Prim(expected)] + +def test_array(): + assert parse("int[5]") == [Prim(lib._CFFI_PRIM_INT), '->', Array(0), 5] + assert parse("int[]") == [Prim(lib._CFFI_PRIM_INT), '->', OpenArray(0)] + assert parse("int[5][8]") == [Prim(lib._CFFI_PRIM_INT), + '->', Array(3), + 5, + Array(0), + 8] + assert parse("int[][8]") == [Prim(lib._CFFI_PRIM_INT), + '->', OpenArray(2), + Array(0), + 8] + +def test_pointer(): + assert parse("int*") == [Prim(lib._CFFI_PRIM_INT), '->', Pointer(0)] + assert parse("int***") == [Prim(lib._CFFI_PRIM_INT), + Pointer(0), Pointer(1), '->', Pointer(2)] + +def test_grouping(): + assert parse("int*[]") == [Prim(lib._CFFI_PRIM_INT), + Pointer(0), '->', OpenArray(1)] + assert parse("int**[][8]") == [Prim(lib._CFFI_PRIM_INT), + Pointer(0), Pointer(1), + '->', OpenArray(4), Array(2), 8] + assert parse("int(*)[]") == [Prim(lib._CFFI_PRIM_INT), + NoOp(3), '->', Pointer(1), OpenArray(0)] + assert parse("int(*)[][8]") == [Prim(lib._CFFI_PRIM_INT), + NoOp(3), '->', Pointer(1), + OpenArray(4), Array(0), 8] + assert parse("int**(**)") == [Prim(lib._CFFI_PRIM_INT), + Pointer(0), Pointer(1), + NoOp(2), Pointer(3), '->', Pointer(4)] + assert parse("int**(**)[]") == [Prim(lib._CFFI_PRIM_INT), + Pointer(0), Pointer(1), + NoOp(6), Pointer(3), '->', Pointer(4), + OpenArray(2)] + +def test_simple_function(): + assert parse("int()") == [Prim(lib._CFFI_PRIM_INT), + '->', Func(0), FuncEnd(0), 0] + assert parse("int(int)") == [Prim(lib._CFFI_PRIM_INT), + '->', Func(0), NoOp(4), FuncEnd(0), + Prim(lib._CFFI_PRIM_INT)] + assert parse("int(long, char)") == [ + Prim(lib._CFFI_PRIM_INT), + '->', Func(0), NoOp(5), NoOp(6), FuncEnd(0), + Prim(lib._CFFI_PRIM_LONG), + Prim(lib._CFFI_PRIM_CHAR)] + assert parse("int(int*)") == [Prim(lib._CFFI_PRIM_INT), + '->', Func(0), NoOp(5), FuncEnd(0), + Prim(lib._CFFI_PRIM_INT), + Pointer(4)] + assert parse("int*(void)") == [Prim(lib._CFFI_PRIM_INT), + Pointer(0), + '->', Func(1), FuncEnd(0), 0] + assert parse("int(int, ...)") == [Prim(lib._CFFI_PRIM_INT), + '->', Func(0), NoOp(5), FuncEnd(1), 0, + Prim(lib._CFFI_PRIM_INT)] + +def test_internal_function(): + assert parse("int(*)()") == [Prim(lib._CFFI_PRIM_INT), + NoOp(3), '->', Pointer(1), + Func(0), FuncEnd(0), 0] + assert parse("int(*())[]") == [Prim(lib._CFFI_PRIM_INT), + NoOp(6), Pointer(1), + '->', Func(2), FuncEnd(0), 0, + OpenArray(0)] + assert parse("int(char(*)(long, short))") == [ + Prim(lib._CFFI_PRIM_INT), + '->', Func(0), NoOp(6), FuncEnd(0), + Prim(lib._CFFI_PRIM_CHAR), + NoOp(7), Pointer(5), + Func(4), NoOp(11), NoOp(12), FuncEnd(0), + Prim(lib._CFFI_PRIM_LONG), + Prim(lib._CFFI_PRIM_SHORT)] + +def test_fix_arg_types(): + assert parse("int(char(long, short))") == [ + Prim(lib._CFFI_PRIM_INT), + '->', Func(0), Pointer(5), FuncEnd(0), + Prim(lib._CFFI_PRIM_CHAR), + Func(4), NoOp(9), NoOp(10), FuncEnd(0), + Prim(lib._CFFI_PRIM_LONG), + Prim(lib._CFFI_PRIM_SHORT)] + assert parse("int(char[])") == [ + Prim(lib._CFFI_PRIM_INT), + '->', Func(0), Pointer(4), FuncEnd(0), + Prim(lib._CFFI_PRIM_CHAR), + OpenArray(4)] + +def test_enum(): + for i in range(len(enum_names)): + assert parse("enum %s" % (enum_names[i],)) == ['->', Enum(i)] + assert parse("enum %s*" % (enum_names[i],)) == [Enum(i), + '->', Pointer(0)] + +def test_error(): + parse_error("short short int", "'short' after another 'short' or 'long'", 6) + parse_error("long long long", "'long long long' is too long", 10) + parse_error("short long", "'long' after 'short'", 6) + parse_error("signed unsigned int", "multiple 'signed' or 'unsigned'", 7) + parse_error("unsigned signed int", "multiple 'signed' or 'unsigned'", 9) + parse_error("long char", "invalid combination of types", 5) + parse_error("short char", "invalid combination of types", 6) + parse_error("signed void", "invalid combination of types", 7) + parse_error("unsigned struct", "invalid combination of types", 9) + # + parse_error("", "identifier expected", 0) + parse_error("]", "identifier expected", 0) + parse_error("*", "identifier expected", 0) + parse_error("int ]**", "unexpected symbol", 4) + parse_error("char char", "unexpected symbol", 5) + parse_error("int(int]", "expected ')'", 7) + parse_error("int(*]", "expected ')'", 5) + parse_error("int(]", "identifier expected", 4) + parse_error("int[?]", "expected a positive integer constant", 4) + parse_error("int[24)", "expected ']'", 6) + parse_error("struct", "struct or union name expected", 6) + parse_error("struct 24", "struct or union name expected", 7) + parse_error("int[5](*)", "unexpected symbol", 6) + parse_error("int a(*)", "identifier expected", 6) + parse_error("int[123456789012345678901234567890]", "number too large", 4) + # + parse_error("_Complex", "identifier expected", 0) + parse_error("int _Complex", "_Complex type combination unsupported", 4) + parse_error("long double _Complex", "_Complex type combination unsupported", + 12) + +def test_number_too_large(): + num_max = sys.maxsize + assert parse("char[%d]" % num_max) == [Prim(lib._CFFI_PRIM_CHAR), + '->', Array(0), num_max] + parse_error("char[%d]" % (num_max + 1), "number too large", 5) + +def test_complexity_limit(): + parse_error("int" + "[]" * 2500, "internal type complexity limit reached", + 202) + +def test_struct(): + for i in range(len(struct_names)): + if i == 3: + tag = "union" + else: + tag = "struct" + assert parse("%s %s" % (tag, struct_names[i])) == ['->', Struct(i)] + assert parse("%s %s*" % (tag, struct_names[i])) == [Struct(i), + '->', Pointer(0)] + +def test_exchanging_struct_union(): + parse_error("union %s" % (struct_names[0],), + "wrong kind of tag: struct vs union", 6) + parse_error("struct %s" % (struct_names[3],), + "wrong kind of tag: struct vs union", 7) + +def test_identifier(): + for i in range(len(identifier_names)): + assert parse("%s" % (identifier_names[i])) == ['->', Typename(i)] + assert parse("%s*" % (identifier_names[i])) == [Typename(i), + '->', Pointer(0)] + +def test_cffi_opcode_sync(): + import cffi.model + for name in dir(lib): + if name.startswith('_CFFI_'): + assert getattr(cffi_opcode, name[6:]) == getattr(lib, name) + assert sorted(cffi_opcode.PRIMITIVE_TO_INDEX.keys()) == ( + sorted(cffi.model.PrimitiveType.ALL_PRIMITIVE_TYPES.keys())) + +def test_array_length_from_constant(): + parse_error("int[UNKNOWN]", "expected a positive integer constant", 4) + assert parse("int[FIVE]") == [Prim(lib._CFFI_PRIM_INT), '->', Array(0), 5] + assert parse("int[ZERO]") == [Prim(lib._CFFI_PRIM_INT), '->', Array(0), 0] + parse_error("int[NEG]", "expected a positive integer constant", 4) + +def test_various_constant_exprs(): + def array(n): + return [Prim(lib._CFFI_PRIM_CHAR), '->', Array(0), n] + assert parse("char[21]") == array(21) + assert parse("char[0x10]") == array(16) + assert parse("char[0X21]") == array(33) + assert parse("char[0Xb]") == array(11) + assert parse("char[0x1C]") == array(0x1C) + assert parse("char[0xc6]") == array(0xC6) + assert parse("char[010]") == array(8) + assert parse("char[021]") == array(17) + parse_error("char[08]", "invalid number", 5) + parse_error("char[1C]", "invalid number", 5) + parse_error("char[0C]", "invalid number", 5) + # not supported (really obscure): + # "char[+5]" + # "char['A']" + +def test_stdcall_cdecl(): + assert parse("int __stdcall(int)") == [Prim(lib._CFFI_PRIM_INT), + '->', Func(0), NoOp(4), FuncEnd(2), + Prim(lib._CFFI_PRIM_INT)] + assert parse("int __stdcall func(int)") == parse("int __stdcall(int)") + assert parse("int (__stdcall *)()") == [Prim(lib._CFFI_PRIM_INT), + NoOp(3), '->', Pointer(1), + Func(0), FuncEnd(2), 0] + assert parse("int (__stdcall *p)()") == parse("int (__stdcall*)()") + parse_error("__stdcall int", "identifier expected", 0) + parse_error("__cdecl int", "identifier expected", 0) + parse_error("int __stdcall", "expected '('", 13) + parse_error("int __cdecl", "expected '('", 11) diff --git a/testing/cffi1/test_pkgconfig.py b/testing/cffi1/test_pkgconfig.py new file mode 100644 index 0000000..c725cca --- /dev/null +++ b/testing/cffi1/test_pkgconfig.py @@ -0,0 +1,94 @@ +import sys +import subprocess +import py +import cffi.pkgconfig as pkgconfig +from cffi import PkgConfigError + + +def mock_call(libname, flag): + assert libname=="foobarbaz" + flags = { + "--cflags": "-I/usr/include/python3.6m -DABCD -DCFFI_TEST=1 -O42\n", + "--libs": "-L/usr/lib64 -lpython3.6 -shared\n", + } + return flags[flag] + + +def test_merge_flags(): + d1 = {"ham": [1, 2, 3], "spam" : ["a", "b", "c"], "foo" : []} + d2 = {"spam" : ["spam", "spam", "spam"], "bar" : ["b", "a", "z"]} + + pkgconfig.merge_flags(d1, d2) + assert d1 == { + "ham": [1, 2, 3], + "spam" : ["a", "b", "c", "spam", "spam", "spam"], + "bar" : ["b", "a", "z"], + "foo" : []} + + +def test_pkgconfig(): + assert pkgconfig.flags_from_pkgconfig([]) == {} + + saved = pkgconfig.call + try: + pkgconfig.call = mock_call + flags = pkgconfig.flags_from_pkgconfig(["foobarbaz"]) + finally: + pkgconfig.call = saved + assert flags == { + 'include_dirs': ['/usr/include/python3.6m'], + 'library_dirs': ['/usr/lib64'], + 'libraries': ['python3.6'], + 'define_macros': [('ABCD', None), ('CFFI_TEST', '1')], + 'extra_compile_args': ['-O42'], + 'extra_link_args': ['-shared'] + } + +class mock_subprocess: + PIPE = Ellipsis + class Popen: + def __init__(self, cmd, stdout, stderr): + if mock_subprocess.RESULT is None: + raise OSError("oops can't run") + assert cmd == ['pkg-config', '--print-errors', '--cflags', 'libfoo'] + def communicate(self): + bout, berr, rc = mock_subprocess.RESULT + self.returncode = rc + return bout, berr + +def test_call(): + saved = pkgconfig.subprocess + try: + pkgconfig.subprocess = mock_subprocess + + mock_subprocess.RESULT = None + e = py.test.raises(PkgConfigError, pkgconfig.call, "libfoo", "--cflags") + assert str(e.value) == "cannot run pkg-config: oops can't run" + + mock_subprocess.RESULT = b"", "Foo error!\n", 1 + e = py.test.raises(PkgConfigError, pkgconfig.call, "libfoo", "--cflags") + assert str(e.value) == "Foo error!" + + mock_subprocess.RESULT = b"abc\\def\n", "", 0 + e = py.test.raises(PkgConfigError, pkgconfig.call, "libfoo", "--cflags") + assert str(e.value).startswith("pkg-config --cflags libfoo returned an " + "unsupported backslash-escaped output:") + + mock_subprocess.RESULT = b"abc def\n", "", 0 + result = pkgconfig.call("libfoo", "--cflags") + assert result == "abc def\n" + + mock_subprocess.RESULT = b"abc def\n", "", 0 + result = pkgconfig.call("libfoo", "--cflags") + assert result == "abc def\n" + + if sys.version_info >= (3,): + mock_subprocess.RESULT = b"\xff\n", "", 0 + e = py.test.raises(PkgConfigError, pkgconfig.call, + "libfoo", "--cflags", encoding="utf-8") + assert str(e.value) == ( + "pkg-config --cflags libfoo returned bytes that cannot be " + "decoded with encoding 'utf-8':\nb'\\xff\\n'") + + finally: + pkgconfig.subprocess = saved diff --git a/testing/cffi1/test_re_python.py b/testing/cffi1/test_re_python.py new file mode 100644 index 0000000..377c29b --- /dev/null +++ b/testing/cffi1/test_re_python.py @@ -0,0 +1,256 @@ +import sys, os +import py +from cffi import FFI +from cffi import recompiler, ffiplatform, VerificationMissing +from testing.udir import udir +from testing.support import u + + +def setup_module(mod): + SRC = """ + #include + #define FOOBAR (-42) + static const int FOOBAZ = -43; + #define BIGPOS 420000000000L + #define BIGNEG -420000000000L + int add42(int x) { return x + 42; } + int add43(int x, ...) { return x; } + int globalvar42 = 1234; + const int globalconst42 = 4321; + const char *const globalconsthello = "hello"; + struct foo_s; + typedef struct bar_s { int x; signed char a[]; } bar_t; + enum foo_e { AA, BB, CC }; + + void init_test_re_python(void) { } /* windows hack */ + void PyInit__test_re_python(void) { } /* windows hack */ + """ + tmpdir = udir.join('test_re_python') + tmpdir.ensure(dir=1) + c_file = tmpdir.join('_test_re_python.c') + c_file.write(SRC) + ext = ffiplatform.get_extension( + str(c_file), + '_test_re_python', + export_symbols=['add42', 'add43', 'globalvar42', + 'globalconst42', 'globalconsthello'] + ) + outputfilename = ffiplatform.compile(str(tmpdir), ext) + + # test with a non-ascii char + ofn, oext = os.path.splitext(outputfilename) + if sys.platform == "win32": + unicode_name = ofn + (u+'\u03be') + oext + else: + unicode_name = ofn + (u+'\xe9') + oext + try: + unicode_name.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + unicode_name = None + if unicode_name is not None: + print(repr(outputfilename) + ' ==> ' + repr(unicode_name)) + os.rename(outputfilename, unicode_name) + outputfilename = unicode_name + + mod.extmod = outputfilename + mod.tmpdir = tmpdir + # + ffi = FFI() + ffi.cdef(""" + #define FOOBAR -42 + static const int FOOBAZ = -43; + #define BIGPOS 420000000000L + #define BIGNEG -420000000000L + int add42(int); + int add43(int, ...); + int globalvar42; + const int globalconst42; + const char *const globalconsthello = "hello"; + int no_such_function(int); + int no_such_globalvar; + struct foo_s; + typedef struct bar_s { int x; signed char a[]; } bar_t; + enum foo_e { AA, BB, CC }; + int strlen(const char *); + struct with_union { union { int a; char b; }; }; + union with_struct { struct { int a; char b; }; }; + struct NVGcolor { union { float rgba[4]; struct { float r,g,b,a; }; }; }; + """) + ffi.set_source('re_python_pysrc', None) + ffi.emit_python_code(str(tmpdir.join('re_python_pysrc.py'))) + mod.original_ffi = ffi + # + sys.path.insert(0, str(tmpdir)) + + +def test_constant(): + from re_python_pysrc import ffi + assert ffi.integer_const('FOOBAR') == -42 + assert ffi.integer_const('FOOBAZ') == -43 + +def test_large_constant(): + from re_python_pysrc import ffi + assert ffi.integer_const('BIGPOS') == 420000000000 + assert ffi.integer_const('BIGNEG') == -420000000000 + +def test_function(): + import _cffi_backend + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + assert lib.add42(-10) == 32 + assert type(lib.add42) is _cffi_backend.FFI.CData + +def test_function_with_varargs(): + import _cffi_backend + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod, 0) + assert lib.add43(45, ffi.cast("int", -5)) == 45 + assert type(lib.add43) is _cffi_backend.FFI.CData + +def test_dlopen_none(): + import _cffi_backend + from re_python_pysrc import ffi + name = None + if sys.platform == 'win32': + import ctypes.util + name = ctypes.util.find_msvcrt() + if name is None: + py.test.skip("dlopen(None) cannot work on Windows with Python 3") + lib = ffi.dlopen(name) + assert lib.strlen(b"hello") == 5 + +def test_dlclose(): + import _cffi_backend + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + ffi.dlclose(lib) + if type(extmod) is not str: # unicode, on python 2 + str_extmod = extmod.encode('utf-8') + else: + str_extmod = extmod + e = py.test.raises(ffi.error, getattr, lib, 'add42') + assert str(e.value) == ( + "library '%s' has been closed" % (str_extmod,)) + ffi.dlclose(lib) # does not raise + +def test_constant_via_lib(): + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + assert lib.FOOBAR == -42 + assert lib.FOOBAZ == -43 + +def test_opaque_struct(): + from re_python_pysrc import ffi + ffi.cast("struct foo_s *", 0) + py.test.raises(TypeError, ffi.new, "struct foo_s *") + +def test_nonopaque_struct(): + from re_python_pysrc import ffi + for p in [ffi.new("struct bar_s *", [5, b"foobar"]), + ffi.new("bar_t *", [5, b"foobar"])]: + assert p.x == 5 + assert p.a[0] == ord('f') + assert p.a[5] == ord('r') + +def test_enum(): + from re_python_pysrc import ffi + assert ffi.integer_const("BB") == 1 + e = ffi.cast("enum foo_e", 2) + assert ffi.string(e) == "CC" + +def test_include_1(): + sub_ffi = FFI() + sub_ffi.cdef("static const int k2 = 121212;") + sub_ffi.include(original_ffi) + assert 'macro FOOBAR' in original_ffi._parser._declarations + assert 'macro FOOBAZ' in original_ffi._parser._declarations + sub_ffi.set_source('re_python_pysrc', None) + sub_ffi.emit_python_code(str(tmpdir.join('_re_include_1.py'))) + # + if sys.version_info[:2] >= (3, 3): + import importlib + importlib.invalidate_caches() # issue 197 (but can't reproduce myself) + # + from _re_include_1 import ffi + assert ffi.integer_const('FOOBAR') == -42 + assert ffi.integer_const('FOOBAZ') == -43 + assert ffi.integer_const('k2') == 121212 + lib = ffi.dlopen(extmod) # <- a random unrelated library would be fine + assert lib.FOOBAR == -42 + assert lib.FOOBAZ == -43 + assert lib.k2 == 121212 + # + p = ffi.new("bar_t *", [5, b"foobar"]) + assert p.a[4] == ord('a') + +def test_global_var(): + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + assert lib.globalvar42 == 1234 + p = ffi.addressof(lib, 'globalvar42') + lib.globalvar42 += 5 + assert p[0] == 1239 + p[0] -= 1 + assert lib.globalvar42 == 1238 + +def test_global_const_int(): + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + assert lib.globalconst42 == 4321 + py.test.raises(AttributeError, ffi.addressof, lib, 'globalconst42') + +def test_global_const_nonint(): + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + assert ffi.string(lib.globalconsthello, 8) == b"hello" + py.test.raises(AttributeError, ffi.addressof, lib, 'globalconsthello') + +def test_rtld_constants(): + from re_python_pysrc import ffi + ffi.RTLD_NOW # check that we have the attributes + ffi.RTLD_LAZY + ffi.RTLD_GLOBAL + +def test_no_such_function_or_global_var(): + from re_python_pysrc import ffi + lib = ffi.dlopen(extmod) + e = py.test.raises(ffi.error, getattr, lib, 'no_such_function') + assert str(e.value).startswith( + "symbol 'no_such_function' not found in library '") + e = py.test.raises(ffi.error, getattr, lib, 'no_such_globalvar') + assert str(e.value).startswith( + "symbol 'no_such_globalvar' not found in library '") + +def test_check_version(): + import _cffi_backend + e = py.test.raises(ImportError, _cffi_backend.FFI, + "foobar", _version=0x2594) + assert str(e.value).startswith( + "cffi out-of-line Python module 'foobar' has unknown version") + +def test_partial_enum(): + ffi = FFI() + ffi.cdef("enum foo { A, B, ... };") + ffi.set_source('test_partial_enum', None) + py.test.raises(VerificationMissing, ffi.emit_python_code, + str(tmpdir.join('test_partial_enum.py'))) + +def test_anonymous_union_inside_struct(): + # based on issue #357 + from re_python_pysrc import ffi + INT = ffi.sizeof("int") + assert ffi.offsetof("struct with_union", "a") == 0 + assert ffi.offsetof("struct with_union", "b") == 0 + assert ffi.sizeof("struct with_union") == INT + # + assert ffi.offsetof("union with_struct", "a") == 0 + assert ffi.offsetof("union with_struct", "b") == INT + assert ffi.sizeof("union with_struct") >= INT + 1 + # + FLOAT = ffi.sizeof("float") + assert ffi.sizeof("struct NVGcolor") == FLOAT * 4 + assert ffi.offsetof("struct NVGcolor", "rgba") == 0 + assert ffi.offsetof("struct NVGcolor", "r") == 0 + assert ffi.offsetof("struct NVGcolor", "g") == FLOAT + assert ffi.offsetof("struct NVGcolor", "b") == FLOAT * 2 + assert ffi.offsetof("struct NVGcolor", "a") == FLOAT * 3 diff --git a/testing/cffi1/test_realize_c_type.py b/testing/cffi1/test_realize_c_type.py new file mode 100644 index 0000000..a1f31e6 --- /dev/null +++ b/testing/cffi1/test_realize_c_type.py @@ -0,0 +1,73 @@ +import py, sys +from cffi import cffi_opcode + + +def check(input, expected_output=None, expected_ffi_error=False): + import _cffi_backend + ffi = _cffi_backend.FFI() + if not expected_ffi_error: + ct = ffi.typeof(input) + assert isinstance(ct, ffi.CType) + assert ct.cname == (expected_output or input) + else: + e = py.test.raises(ffi.error, ffi.typeof, input) + if isinstance(expected_ffi_error, str): + assert str(e.value) == expected_ffi_error + +def test_void(): + check("void", "void") + check(" void ", "void") + +def test_int_star(): + check("int") + check("int *") + check("int*", "int *") + check("long int", "long") + check("long") + +def test_noop(): + check("int(*)", "int *") + +def test_array(): + check("int[6]") + +def test_funcptr(): + check("int(*)(long)") + check("int(long)", expected_ffi_error="the type 'int(long)' is a" + " function type, not a pointer-to-function type") + check("int(void)", expected_ffi_error="the type 'int()' is a" + " function type, not a pointer-to-function type") + +def test_funcptr_rewrite_args(): + check("int(*)(int(int))", "int(*)(int(*)(int))") + check("int(*)(long[])", "int(*)(long *)") + check("int(*)(long[5])", "int(*)(long *)") + +def test_all_primitives(): + for name in cffi_opcode.PRIMITIVE_TO_INDEX: + check(name, name) + +def check_func(input, expected_output=None): + import _cffi_backend + ffi = _cffi_backend.FFI() + ct = ffi.typeof(ffi.callback(input, lambda: None)) + assert isinstance(ct, ffi.CType) + if sys.platform != 'win32' or sys.maxsize > 2**32: + expected_output = expected_output.replace('__stdcall *', '*') + assert ct.cname == expected_output + +def test_funcptr_stdcall(): + check_func("int(int)", "int(*)(int)") + check_func("int foobar(int)", "int(*)(int)") + check_func("int __stdcall(int)", "int(__stdcall *)(int)") + check_func("int __stdcall foobar(int)", "int(__stdcall *)(int)") + check_func("void __cdecl(void)", "void(*)()") + check_func("void __cdecl foobar(void)", "void(*)()") + check_func("void __stdcall(void)", "void(__stdcall *)()") + check_func("void __stdcall foobar(long, short)", + "void(__stdcall *)(long, short)") + check_func("void(void __cdecl(void), void __stdcall(void))", + "void(*)(void(*)(), void(__stdcall *)())") + +def test_variadic_overrides_stdcall(): + check("void (__stdcall*)(int, ...)", "void(*)(int, ...)") diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py new file mode 100644 index 0000000..6a31110 --- /dev/null +++ b/testing/cffi1/test_recompiler.py @@ -0,0 +1,2316 @@ + +import sys, os, py +from cffi import FFI, VerificationError, FFIError, CDefError +from cffi import recompiler +from testing.udir import udir +from testing.support import u, long +from testing.support import FdWriteCapture, StdErrCapture, _verify + +try: + import importlib +except ImportError: + importlib = None + + +def check_type_table(input, expected_output, included=None): + ffi = FFI() + if included: + ffi1 = FFI() + ffi1.cdef(included) + ffi.include(ffi1) + ffi.cdef(input) + recomp = recompiler.Recompiler(ffi, 'testmod') + recomp.collect_type_table() + assert ''.join(map(str, recomp.cffi_types)) == expected_output + +def verify(ffi, module_name, source, *args, **kwds): + no_cpp = kwds.pop('no_cpp', False) + kwds.setdefault('undef_macros', ['NDEBUG']) + module_name = '_CFFI_' + module_name + ffi.set_source(module_name, source) + if not os.environ.get('NO_CPP') and not no_cpp: # test the .cpp mode too + kwds.setdefault('source_extension', '.cpp') + source = 'extern "C" {\n%s\n}' % (source,) + elif sys.platform != 'win32': + # add '-Werror' to the existing 'extra_compile_args' flags + kwds['extra_compile_args'] = (kwds.get('extra_compile_args', []) + + ['-Werror']) + return _verify(ffi, module_name, source, *args, **kwds) + +def test_set_source_no_slashes(): + ffi = FFI() + py.test.raises(ValueError, ffi.set_source, "abc/def", None) + py.test.raises(ValueError, ffi.set_source, "abc/def", "C code") + + +def test_type_table_func(): + check_type_table("double sin(double);", + "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)") + check_type_table("float sin(double);", + "(FUNCTION 3)(PRIMITIVE 14)(FUNCTION_END 0)(PRIMITIVE 13)") + check_type_table("float sin(void);", + "(FUNCTION 2)(FUNCTION_END 0)(PRIMITIVE 13)") + check_type_table("double sin(float); double cos(float);", + "(FUNCTION 3)(PRIMITIVE 13)(FUNCTION_END 0)(PRIMITIVE 14)") + check_type_table("double sin(float); double cos(double);", + "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)" # cos + "(FUNCTION 1)(PRIMITIVE 13)(FUNCTION_END 0)") # sin + check_type_table("float sin(double); float cos(float);", + "(FUNCTION 4)(PRIMITIVE 14)(FUNCTION_END 0)" # sin + "(FUNCTION 4)(PRIMITIVE 13)(FUNCTION_END 0)") # cos + +def test_type_table_use_noop_for_repeated_args(): + check_type_table("double sin(double *, double *);", + "(FUNCTION 4)(POINTER 4)(NOOP 1)(FUNCTION_END 0)" + "(PRIMITIVE 14)") + check_type_table("double sin(double *, double *, double);", + "(FUNCTION 3)(POINTER 3)(NOOP 1)(PRIMITIVE 14)" + "(FUNCTION_END 0)") + +def test_type_table_dont_use_noop_for_primitives(): + check_type_table("double sin(double, double);", + "(FUNCTION 1)(PRIMITIVE 14)(PRIMITIVE 14)(FUNCTION_END 0)") + +def test_type_table_funcptr_as_argument(): + check_type_table("int sin(double(float));", + "(FUNCTION 6)(PRIMITIVE 13)(FUNCTION_END 0)" + "(FUNCTION 7)(POINTER 0)(FUNCTION_END 0)" + "(PRIMITIVE 14)(PRIMITIVE 7)") + +def test_type_table_variadic_function(): + check_type_table("int sin(int, ...);", + "(FUNCTION 1)(PRIMITIVE 7)(FUNCTION_END 1)(POINTER 0)") + +def test_type_table_array(): + check_type_table("int a[100];", + "(PRIMITIVE 7)(ARRAY 0)(None 100)") + +def test_type_table_typedef(): + check_type_table("typedef int foo_t;", + "(PRIMITIVE 7)") + +def test_type_table_prebuilt_type(): + check_type_table("int32_t f(void);", + "(FUNCTION 2)(FUNCTION_END 0)(PRIMITIVE 21)") + +def test_type_table_struct_opaque(): + check_type_table("struct foo_s;", + "(STRUCT_UNION 0)") + +def test_type_table_struct(): + check_type_table("struct foo_s { int a; long b; };", + "(PRIMITIVE 7)(PRIMITIVE 9)(STRUCT_UNION 0)") + +def test_type_table_union(): + check_type_table("union foo_u { int a; long b; };", + "(PRIMITIVE 7)(PRIMITIVE 9)(STRUCT_UNION 0)") + +def test_type_table_struct_used(): + check_type_table("struct foo_s { int a; long b; }; int f(struct foo_s*);", + "(FUNCTION 3)(POINTER 5)(FUNCTION_END 0)" + "(PRIMITIVE 7)(PRIMITIVE 9)" + "(STRUCT_UNION 0)") + +def test_type_table_anonymous_struct_with_typedef(): + check_type_table("typedef struct { int a; long b; } foo_t;", + "(STRUCT_UNION 0)(PRIMITIVE 7)(PRIMITIVE 9)") + +def test_type_table_enum(): + check_type_table("enum foo_e { AA, BB, ... };", + "(ENUM 0)") + +def test_type_table_include_1(): + check_type_table("foo_t sin(foo_t);", + "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)", + included="typedef double foo_t;") + +def test_type_table_include_2(): + check_type_table("struct foo_s *sin(struct foo_s *);", + "(FUNCTION 1)(POINTER 3)(FUNCTION_END 0)(STRUCT_UNION 0)", + included="struct foo_s { int x, y; };") + + +def test_math_sin(): + import math + ffi = FFI() + ffi.cdef("float sin(double); double cos(double);") + lib = verify(ffi, 'test_math_sin', '#include ') + assert lib.cos(1.43) == math.cos(1.43) + +def test_repr_lib(): + ffi = FFI() + lib = verify(ffi, 'test_repr_lib', '') + assert repr(lib) == "" + +def test_funcarg_ptr(): + ffi = FFI() + ffi.cdef("int foo(int *);") + lib = verify(ffi, 'test_funcarg_ptr', 'int foo(int *p) { return *p; }') + assert lib.foo([-12345]) == -12345 + +def test_funcres_ptr(): + ffi = FFI() + ffi.cdef("int *foo(void);") + lib = verify(ffi, 'test_funcres_ptr', + 'int *foo(void) { static int x=-12345; return &x; }') + assert lib.foo()[0] == -12345 + +def test_global_var_array(): + ffi = FFI() + ffi.cdef("int a[100];") + lib = verify(ffi, 'test_global_var_array', 'int a[100] = { 9999 };') + lib.a[42] = 123456 + assert lib.a[42] == 123456 + assert lib.a[0] == 9999 + +def test_verify_typedef(): + ffi = FFI() + ffi.cdef("typedef int **foo_t;") + lib = verify(ffi, 'test_verify_typedef', 'typedef int **foo_t;') + assert ffi.sizeof("foo_t") == ffi.sizeof("void *") + +def test_verify_typedef_dotdotdot(): + ffi = FFI() + ffi.cdef("typedef ... foo_t;") + verify(ffi, 'test_verify_typedef_dotdotdot', 'typedef int **foo_t;') + +def test_verify_typedef_star_dotdotdot(): + ffi = FFI() + ffi.cdef("typedef ... *foo_t;") + verify(ffi, 'test_verify_typedef_star_dotdotdot', 'typedef int **foo_t;') + +def test_global_var_int(): + ffi = FFI() + ffi.cdef("int a, b, c;") + lib = verify(ffi, 'test_global_var_int', 'int a = 999, b, c;') + assert lib.a == 999 + lib.a -= 1001 + assert lib.a == -2 + lib.a = -2147483648 + assert lib.a == -2147483648 + py.test.raises(OverflowError, "lib.a = 2147483648") + py.test.raises(OverflowError, "lib.a = -2147483649") + lib.b = 525 # try with the first access being in setattr, too + assert lib.b == 525 + py.test.raises(AttributeError, "del lib.a") + py.test.raises(AttributeError, "del lib.c") + py.test.raises(AttributeError, "del lib.foobarbaz") + +def test_macro(): + ffi = FFI() + ffi.cdef("#define FOOBAR ...") + lib = verify(ffi, 'test_macro', "#define FOOBAR (-6912)") + assert lib.FOOBAR == -6912 + py.test.raises(AttributeError, "lib.FOOBAR = 2") + +def test_macro_check_value(): + # the value '-0x80000000' in C sources does not have a clear meaning + # to me; it appears to have a different effect than '-2147483648'... + # Moreover, on 32-bits, -2147483648 is actually equal to + # -2147483648U, which in turn is equal to 2147483648U and so positive. + vals = ['42', '-42', '0x80000000', '-2147483648', + '0', '9223372036854775809ULL', + '-9223372036854775807LL'] + if sys.maxsize <= 2**32 or sys.platform == 'win32': + vals.remove('-2147483648') + ffi = FFI() + cdef_lines = ['#define FOO_%d_%d %s' % (i, j, vals[i]) + for i in range(len(vals)) + for j in range(len(vals))] + ffi.cdef('\n'.join(cdef_lines)) + + verify_lines = ['#define FOO_%d_%d %s' % (i, j, vals[j]) # [j], not [i] + for i in range(len(vals)) + for j in range(len(vals))] + lib = verify(ffi, 'test_macro_check_value_ok', + '\n'.join(verify_lines)) + # + for j in range(len(vals)): + c_got = int(vals[j].replace('U', '').replace('L', ''), 0) + c_compiler_msg = str(c_got) + if c_got > 0: + c_compiler_msg += ' (0x%x)' % (c_got,) + # + for i in range(len(vals)): + attrname = 'FOO_%d_%d' % (i, j) + if i == j: + x = getattr(lib, attrname) + assert x == c_got + else: + e = py.test.raises(ffi.error, getattr, lib, attrname) + assert str(e.value) == ( + "the C compiler says '%s' is equal to " + "%s, but the cdef disagrees" % (attrname, c_compiler_msg)) + +def test_constant(): + ffi = FFI() + ffi.cdef("static const int FOOBAR;") + lib = verify(ffi, 'test_constant', "#define FOOBAR (-6912)") + assert lib.FOOBAR == -6912 + py.test.raises(AttributeError, "lib.FOOBAR = 2") + +def test_check_value_of_static_const(): + ffi = FFI() + ffi.cdef("static const int FOOBAR = 042;") + lib = verify(ffi, 'test_check_value_of_static_const', + "#define FOOBAR (-6912)") + e = py.test.raises(ffi.error, getattr, lib, 'FOOBAR') + assert str(e.value) == ( + "the C compiler says 'FOOBAR' is equal to -6912, but the cdef disagrees") + +def test_constant_nonint(): + ffi = FFI() + ffi.cdef("static const double FOOBAR;") + lib = verify(ffi, 'test_constant_nonint', "#define FOOBAR (-6912.5)") + assert lib.FOOBAR == -6912.5 + py.test.raises(AttributeError, "lib.FOOBAR = 2") + +def test_constant_ptr(): + ffi = FFI() + ffi.cdef("static double *const FOOBAR;") + lib = verify(ffi, 'test_constant_ptr', "#define FOOBAR NULL") + assert lib.FOOBAR == ffi.NULL + assert ffi.typeof(lib.FOOBAR) == ffi.typeof("double *") + +def test_dir(): + ffi = FFI() + ffi.cdef("int ff(int); int aa; static const int my_constant;") + lib = verify(ffi, 'test_dir', """ + #define my_constant (-45) + int aa; + int ff(int x) { return x+aa; } + """) + lib.aa = 5 + assert dir(lib) == ['aa', 'ff', 'my_constant'] + # + aaobj = lib.__dict__['aa'] + assert not isinstance(aaobj, int) # some internal object instead + assert lib.__dict__ == { + 'ff': lib.ff, + 'aa': aaobj, + 'my_constant': -45} + lib.__dict__['ff'] = "??" + assert lib.ff(10) == 15 + +def test_verify_opaque_struct(): + ffi = FFI() + ffi.cdef("struct foo_s;") + lib = verify(ffi, 'test_verify_opaque_struct', "struct foo_s;") + assert ffi.typeof("struct foo_s").cname == "struct foo_s" + +def test_verify_opaque_union(): + ffi = FFI() + ffi.cdef("union foo_s;") + lib = verify(ffi, 'test_verify_opaque_union', "union foo_s;") + assert ffi.typeof("union foo_s").cname == "union foo_s" + +def test_verify_struct(): + ffi = FFI() + ffi.cdef("""struct foo_s { int b; short a; ...; }; + struct bar_s { struct foo_s *f; };""") + lib = verify(ffi, 'test_verify_struct', + """struct foo_s { short a; int b; }; + struct bar_s { struct foo_s *f; };""") + ffi.typeof("struct bar_s *") + p = ffi.new("struct foo_s *", {'a': -32768, 'b': -2147483648}) + assert p.a == -32768 + assert p.b == -2147483648 + py.test.raises(OverflowError, "p.a -= 1") + py.test.raises(OverflowError, "p.b -= 1") + q = ffi.new("struct bar_s *", {'f': p}) + assert q.f == p + # + assert ffi.offsetof("struct foo_s", "a") == 0 + assert ffi.offsetof("struct foo_s", "b") == 4 + assert ffi.offsetof(u+"struct foo_s", u+"b") == 4 + # + py.test.raises(TypeError, ffi.addressof, p) + assert ffi.addressof(p[0]) == p + assert ffi.typeof(ffi.addressof(p[0])) is ffi.typeof("struct foo_s *") + assert ffi.typeof(ffi.addressof(p, "b")) is ffi.typeof("int *") + assert ffi.addressof(p, "b")[0] == p.b + +def test_verify_exact_field_offset(): + ffi = FFI() + ffi.cdef("""struct foo_s { int b; short a; };""") + lib = verify(ffi, 'test_verify_exact_field_offset', + """struct foo_s { short a; int b; };""") + e = py.test.raises(ffi.error, ffi.new, "struct foo_s *", []) # lazily + assert str(e.value) == ("struct foo_s: wrong offset for field 'b' (cdef " + 'says 0, but C compiler says 4). fix it or use "...;" ' + "in the cdef for struct foo_s to make it flexible") + +def test_type_caching(): + ffi1 = FFI(); ffi1.cdef("struct foo_s;") + ffi2 = FFI(); ffi2.cdef("struct foo_s;") # different one! + lib1 = verify(ffi1, 'test_type_caching_1', 'struct foo_s;') + lib2 = verify(ffi2, 'test_type_caching_2', 'struct foo_s;') + # shared types + assert ffi1.typeof("long") is ffi2.typeof("long") + assert ffi1.typeof("long**") is ffi2.typeof("long * *") + assert ffi1.typeof("long(*)(int, ...)") is ffi2.typeof("long(*)(int, ...)") + # non-shared types + assert ffi1.typeof("struct foo_s") is not ffi2.typeof("struct foo_s") + assert ffi1.typeof("struct foo_s *") is not ffi2.typeof("struct foo_s *") + assert ffi1.typeof("struct foo_s*(*)()") is not ( + ffi2.typeof("struct foo_s*(*)()")) + assert ffi1.typeof("void(*)(struct foo_s*)") is not ( + ffi2.typeof("void(*)(struct foo_s*)")) + +def test_verify_enum(): + ffi = FFI() + ffi.cdef("""enum e1 { B1, A1, ... }; enum e2 { B2, A2, ... };""") + lib = verify(ffi, 'test_verify_enum', + "enum e1 { A1, B1, C1=%d };" % sys.maxsize + + "enum e2 { A2, B2, C2 };") + ffi.typeof("enum e1") + ffi.typeof("enum e2") + assert lib.A1 == 0 + assert lib.B1 == 1 + assert lib.A2 == 0 + assert lib.B2 == 1 + assert ffi.sizeof("enum e1") == ffi.sizeof("long") + assert ffi.sizeof("enum e2") == ffi.sizeof("int") + assert repr(ffi.cast("enum e1", 0)) == "" + +def test_duplicate_enum(): + ffi = FFI() + ffi.cdef("enum e1 { A1, ... }; enum e2 { A1, ... };") + py.test.raises(VerificationError, verify, ffi, 'test_duplicate_enum', + "enum e1 { A1 }; enum e2 { B1 };") + +def test_dotdotdot_length_of_array_field(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[...]; int b[...]; };") + verify(ffi, 'test_dotdotdot_length_of_array_field', + "struct foo_s { int a[42]; int b[11]; };") + assert ffi.sizeof("struct foo_s") == (42 + 11) * 4 + p = ffi.new("struct foo_s *") + assert p.a[41] == p.b[10] == 0 + py.test.raises(IndexError, "p.a[42]") + py.test.raises(IndexError, "p.b[11]") + +def test_dotdotdot_global_array(): + ffi = FFI() + ffi.cdef("int aa[...]; int bb[...];") + lib = verify(ffi, 'test_dotdotdot_global_array', + "int aa[41]; int bb[12];") + assert ffi.sizeof(lib.aa) == 41 * 4 + assert ffi.sizeof(lib.bb) == 12 * 4 + assert lib.aa[40] == lib.bb[11] == 0 + py.test.raises(IndexError, "lib.aa[41]") + py.test.raises(IndexError, "lib.bb[12]") + +def test_misdeclared_field_1(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[5]; };") + try: + verify(ffi, 'test_misdeclared_field_1', + "struct foo_s { int a[6]; };") + except VerificationError: + pass # ok, fail during compilation already (e.g. C++) + else: + assert ffi.sizeof("struct foo_s") == 24 # found by the actual C code + try: + # lazily build the fields and boom: + p = ffi.new("struct foo_s *") + p.a + assert False, "should have raised" + except ffi.error as e: + assert str(e).startswith("struct foo_s: wrong size for field 'a' " + "(cdef says 20, but C compiler says 24)") + +def test_open_array_in_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { int b; int a[]; };") + verify(ffi, 'test_open_array_in_struct', + "struct foo_s { int b; int a[]; };") + assert ffi.sizeof("struct foo_s") == 4 + p = ffi.new("struct foo_s *", [5, [10, 20, 30, 40]]) + assert p.a[2] == 30 + assert ffi.sizeof(p) == ffi.sizeof("void *") + assert ffi.sizeof(p[0]) == 5 * ffi.sizeof("int") + +def test_math_sin_type(): + ffi = FFI() + ffi.cdef("double sin(double); void *xxtestfunc();") + lib = verify(ffi, 'test_math_sin_type', """ + #include + void *xxtestfunc(void) { return 0; } + """) + # 'lib.sin' is typed as a object on lib + assert ffi.typeof(lib.sin).cname == "double(*)(double)" + # 'x' is another object on lib, made very indirectly + x = type(lib).__dir__.__get__(lib) + py.test.raises(TypeError, ffi.typeof, x) + # + # present on built-in functions on CPython; must be emulated on PyPy: + assert lib.sin.__name__ == 'sin' + assert lib.sin.__module__ == '_CFFI_test_math_sin_type' + assert lib.sin.__doc__ == ( + "double sin(double);\n" + "\n" + "CFFI C function from _CFFI_test_math_sin_type.lib") + + assert ffi.typeof(lib.xxtestfunc).cname == "void *(*)()" + assert lib.xxtestfunc.__doc__ == ( + "void *xxtestfunc();\n" + "\n" + "CFFI C function from _CFFI_test_math_sin_type.lib") + +def test_verify_anonymous_struct_with_typedef(): + ffi = FFI() + ffi.cdef("typedef struct { int a; long b; ...; } foo_t;") + verify(ffi, 'test_verify_anonymous_struct_with_typedef', + "typedef struct { long b; int hidden, a; } foo_t;") + p = ffi.new("foo_t *", {'b': 42}) + assert p.b == 42 + assert repr(p).startswith("" + # + ffi = FFI() + ffi.cdef("typedef enum { AA=%d } e1;" % sys.maxsize) + lib = verify(ffi, 'test_verify_anonymous_enum_with_typedef2', + "typedef enum { AA=%d } e1;" % sys.maxsize) + assert lib.AA == int(ffi.cast("long", sys.maxsize)) + assert ffi.sizeof("e1") == ffi.sizeof("long") + +def test_unique_types(): + CDEF = "struct foo_s; union foo_u; enum foo_e { AA };" + ffi1 = FFI(); ffi1.cdef(CDEF); verify(ffi1, "test_unique_types_1", CDEF) + ffi2 = FFI(); ffi2.cdef(CDEF); verify(ffi2, "test_unique_types_2", CDEF) + # + assert ffi1.typeof("char") is ffi2.typeof("char ") + assert ffi1.typeof("long") is ffi2.typeof("signed long int") + assert ffi1.typeof("double *") is ffi2.typeof("double*") + assert ffi1.typeof("int ***") is ffi2.typeof(" int * * *") + assert ffi1.typeof("int[]") is ffi2.typeof("signed int[]") + assert ffi1.typeof("signed int*[17]") is ffi2.typeof("int *[17]") + assert ffi1.typeof("void") is ffi2.typeof("void") + assert ffi1.typeof("int(*)(int,int)") is ffi2.typeof("int(*)(int,int)") + # + # these depend on user-defined data, so should not be shared + for name in ["struct foo_s", + "union foo_u *", + "enum foo_e", + "struct foo_s *(*)()", + "void(*)(struct foo_s *)", + "struct foo_s *(*[5])[8]", + ]: + assert ffi1.typeof(name) is not ffi2.typeof(name) + # sanity check: twice 'ffi1' + assert ffi1.typeof("struct foo_s*") is ffi1.typeof("struct foo_s *") + +def test_module_name_in_package(): + ffi = FFI() + ffi.cdef("int foo(int);") + recompiler.recompile(ffi, "test_module_name_in_package.mymod", + "int foo(int x) { return x + 32; }", + tmpdir=str(udir)) + old_sys_path = sys.path[:] + try: + package_dir = udir.join('test_module_name_in_package') + for name in os.listdir(str(udir)): + assert not name.startswith('test_module_name_in_package.') + assert os.path.isdir(str(package_dir)) + assert len(os.listdir(str(package_dir))) > 0 + assert os.path.exists(str(package_dir.join('mymod.c'))) + package_dir.join('__init__.py').write('') + # + getattr(importlib, 'invalidate_caches', object)() + # + sys.path.insert(0, str(udir)) + import test_module_name_in_package.mymod + assert test_module_name_in_package.mymod.lib.foo(10) == 42 + assert test_module_name_in_package.mymod.__name__ == ( + 'test_module_name_in_package.mymod') + finally: + sys.path[:] = old_sys_path + +def test_bad_size_of_global_1(): + ffi = FFI() + ffi.cdef("short glob;") + py.test.raises(VerificationError, verify, ffi, + "test_bad_size_of_global_1", "long glob;") + +def test_bad_size_of_global_2(): + ffi = FFI() + ffi.cdef("int glob[10];") + py.test.raises(VerificationError, verify, ffi, + "test_bad_size_of_global_2", "int glob[9];") + +def test_unspecified_size_of_global_1(): + ffi = FFI() + ffi.cdef("int glob[];") + lib = verify(ffi, "test_unspecified_size_of_global_1", "int glob[10];") + assert ffi.typeof(lib.glob) == ffi.typeof("int *") + +def test_unspecified_size_of_global_2(): + ffi = FFI() + ffi.cdef("int glob[][5];") + lib = verify(ffi, "test_unspecified_size_of_global_2", "int glob[10][5];") + assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]") + +def test_unspecified_size_of_global_3(): + ffi = FFI() + ffi.cdef("int glob[][...];") + lib = verify(ffi, "test_unspecified_size_of_global_3", "int glob[10][5];") + assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]") + +def test_unspecified_size_of_global_4(): + ffi = FFI() + ffi.cdef("int glob[...][...];") + lib = verify(ffi, "test_unspecified_size_of_global_4", "int glob[10][5];") + assert ffi.typeof(lib.glob) == ffi.typeof("int[10][5]") + +def test_include_1(): + ffi1 = FFI() + ffi1.cdef("typedef double foo_t;") + verify(ffi1, "test_include_1_parent", "typedef double foo_t;") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("foo_t ff1(foo_t);") + lib = verify(ffi, "test_include_1", "double ff1(double x) { return 42.5; }") + assert lib.ff1(0) == 42.5 + assert ffi1.typeof("foo_t") is ffi.typeof("foo_t") is ffi.typeof("double") + +def test_include_1b(): + ffi1 = FFI() + ffi1.cdef("int foo1(int);") + lib1 = verify(ffi1, "test_include_1b_parent", + "int foo1(int x) { return x + 10; }") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("int foo2(int);") + lib = verify(ffi, "test_include_1b", "int foo2(int x) { return x - 5; }") + assert lib.foo2(42) == 37 + assert lib.foo1(42) == 52 + assert lib.foo1 is lib1.foo1 + +def test_include_2(): + ffi1 = FFI() + ffi1.cdef("struct foo_s { int x, y; };") + verify(ffi1, "test_include_2_parent", "struct foo_s { int x, y; };") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("struct foo_s *ff2(struct foo_s *);") + lib = verify(ffi, "test_include_2", + "struct foo_s { int x, y; }; //usually from a #include\n" + "struct foo_s *ff2(struct foo_s *p) { p->y++; return p; }") + p = ffi.new("struct foo_s *") + p.y = 41 + q = lib.ff2(p) + assert q == p + assert p.y == 42 + assert ffi1.typeof("struct foo_s") is ffi.typeof("struct foo_s") + +def test_include_3(): + ffi1 = FFI() + ffi1.cdef("typedef short sshort_t;") + verify(ffi1, "test_include_3_parent", "typedef short sshort_t;") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("sshort_t ff3(sshort_t);") + lib = verify(ffi, "test_include_3", + "typedef short sshort_t; //usually from a #include\n" + "sshort_t ff3(sshort_t x) { return x + 42; }") + assert lib.ff3(10) == 52 + assert ffi.typeof(ffi.cast("sshort_t", 42)) is ffi.typeof("short") + assert ffi1.typeof("sshort_t") is ffi.typeof("sshort_t") + +def test_include_4(): + ffi1 = FFI() + ffi1.cdef("typedef struct { int x; } mystruct_t;") + verify(ffi1, "test_include_4_parent", + "typedef struct { int x; } mystruct_t;") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("mystruct_t *ff4(mystruct_t *);") + lib = verify(ffi, "test_include_4", + "typedef struct {int x; } mystruct_t; //usually from a #include\n" + "mystruct_t *ff4(mystruct_t *p) { p->x += 42; return p; }") + p = ffi.new("mystruct_t *", [10]) + q = lib.ff4(p) + assert q == p + assert p.x == 52 + assert ffi1.typeof("mystruct_t") is ffi.typeof("mystruct_t") + +def test_include_5(): + ffi1 = FFI() + ffi1.cdef("typedef struct { int x[2]; int y; } *mystruct_p;") + verify(ffi1, "test_include_5_parent", + "typedef struct { int x[2]; int y; } *mystruct_p;") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("mystruct_p ff5(mystruct_p);") + lib = verify(ffi, "test_include_5", + "typedef struct {int x[2]; int y; } *mystruct_p; //usually #include\n" + "mystruct_p ff5(mystruct_p p) { p->x[1] += 42; return p; }") + assert ffi.alignof(ffi.typeof("mystruct_p").item) == 4 + assert ffi1.typeof("mystruct_p") is ffi.typeof("mystruct_p") + p = ffi.new("mystruct_p", [[5, 10], -17]) + q = lib.ff5(p) + assert q == p + assert p.x[0] == 5 + assert p.x[1] == 52 + assert p.y == -17 + assert ffi.alignof(ffi.typeof(p[0])) == 4 + +def test_include_6(): + ffi1 = FFI() + ffi1.cdef("typedef ... mystruct_t;") + verify(ffi1, "test_include_6_parent", + "typedef struct _mystruct_s mystruct_t;") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("mystruct_t *ff6(void); int ff6b(mystruct_t *);") + lib = verify(ffi, "test_include_6", + "typedef struct _mystruct_s mystruct_t; //usually from a #include\n" + "struct _mystruct_s { int x; };\n" + "static mystruct_t result_struct = { 42 };\n" + "mystruct_t *ff6(void) { return &result_struct; }\n" + "int ff6b(mystruct_t *p) { return p->x; }") + p = lib.ff6() + assert ffi.cast("int *", p)[0] == 42 + assert lib.ff6b(p) == 42 + +def test_include_7(): + ffi1 = FFI() + ffi1.cdef("typedef ... mystruct_t;\n" + "int ff7b(mystruct_t *);") + verify(ffi1, "test_include_7_parent", + "typedef struct { int x; } mystruct_t;\n" + "int ff7b(mystruct_t *p) { return p->x; }") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("mystruct_t *ff7(void);") + lib = verify(ffi, "test_include_7", + "typedef struct { int x; } mystruct_t; //usually from a #include\n" + "static mystruct_t result_struct = { 42 };" + "mystruct_t *ff7(void) { return &result_struct; }") + p = lib.ff7() + assert ffi.cast("int *", p)[0] == 42 + assert lib.ff7b(p) == 42 + +def test_include_8(): + ffi1 = FFI() + ffi1.cdef("struct foo_s;") + verify(ffi1, "test_include_8_parent", "struct foo_s;") + ffi = FFI() + ffi.include(ffi1) + ffi.cdef("struct foo_s { int x, y; };") + verify(ffi, "test_include_8", "struct foo_s { int x, y; };") + e = py.test.raises(NotImplementedError, ffi.new, "struct foo_s *") + assert str(e.value) == ( + "'struct foo_s' is opaque in the ffi.include(), but no longer in " + "the ffi doing the include (workaround: don't use ffi.include() but" + " duplicate the declarations of everything using struct foo_s)") + +def test_unicode_libraries(): + try: + unicode + except NameError: + py.test.skip("for python 2.x") + # + import math + lib_m = "m" + if sys.platform == 'win32': + #there is a small chance this fails on Mingw via environ $CC + import distutils.ccompiler + if distutils.ccompiler.get_default_compiler() == 'msvc': + lib_m = 'msvcrt' + ffi = FFI() + ffi.cdef(unicode("float sin(double); double cos(double);")) + lib = verify(ffi, 'test_math_sin_unicode', unicode('#include '), + libraries=[unicode(lib_m)]) + assert lib.cos(1.43) == math.cos(1.43) + +def test_incomplete_struct_as_arg(): + ffi = FFI() + ffi.cdef("struct foo_s { int x; ...; }; int f(int, struct foo_s);") + lib = verify(ffi, "test_incomplete_struct_as_arg", + "struct foo_s { int a, x, z; };\n" + "int f(int b, struct foo_s s) { return s.x * b; }") + s = ffi.new("struct foo_s *", [21]) + assert s.x == 21 + assert ffi.sizeof(s[0]) == 12 + assert ffi.offsetof(ffi.typeof(s), 'x') == 4 + assert lib.f(2, s[0]) == 42 + assert ffi.typeof(lib.f) == ffi.typeof("int(*)(int, struct foo_s)") + +def test_incomplete_struct_as_result(): + ffi = FFI() + ffi.cdef("struct foo_s { int x; ...; }; struct foo_s f(int);") + lib = verify(ffi, "test_incomplete_struct_as_result", + "struct foo_s { int a, x, z; };\n" + "struct foo_s f(int x) { struct foo_s r; r.x = x * 2; return r; }") + s = lib.f(21) + assert s.x == 42 + assert ffi.typeof(lib.f) == ffi.typeof("struct foo_s(*)(int)") + +def test_incomplete_struct_as_both(): + ffi = FFI() + ffi.cdef("struct foo_s { int x; ...; }; struct bar_s { int y; ...; };\n" + "struct foo_s f(int, struct bar_s);") + lib = verify(ffi, "test_incomplete_struct_as_both", + "struct foo_s { int a, x, z; };\n" + "struct bar_s { int b, c, y, d; };\n" + "struct foo_s f(int x, struct bar_s b) {\n" + " struct foo_s r; r.x = x * b.y; return r;\n" + "}") + b = ffi.new("struct bar_s *", [7]) + s = lib.f(6, b[0]) + assert s.x == 42 + assert ffi.typeof(lib.f) == ffi.typeof( + "struct foo_s(*)(int, struct bar_s)") + s = lib.f(14, {'y': -3}) + assert s.x == -42 + +def test_name_of_unnamed_struct(): + ffi = FFI() + ffi.cdef("typedef struct { int x; } foo_t;\n" + "typedef struct { int y; } *bar_p;\n" + "typedef struct { int y; } **baz_pp;\n") + verify(ffi, "test_name_of_unnamed_struct", + "typedef struct { int x; } foo_t;\n" + "typedef struct { int y; } *bar_p;\n" + "typedef struct { int y; } **baz_pp;\n") + assert repr(ffi.typeof("foo_t")) == "" + assert repr(ffi.typeof("bar_p")) == "" + assert repr(ffi.typeof("baz_pp")) == "" + +def test_address_of_global_var(): + ffi = FFI() + ffi.cdef(""" + long bottom, bottoms[2]; + long FetchRectBottom(void); + long FetchRectBottoms1(void); + #define FOOBAR 42 + """) + lib = verify(ffi, "test_address_of_global_var", """ + long bottom, bottoms[2]; + long FetchRectBottom(void) { return bottom; } + long FetchRectBottoms1(void) { return bottoms[1]; } + #define FOOBAR 42 + """) + lib.bottom = 300 + assert lib.FetchRectBottom() == 300 + lib.bottom += 1 + assert lib.FetchRectBottom() == 301 + lib.bottoms[1] = 500 + assert lib.FetchRectBottoms1() == 500 + lib.bottoms[1] += 2 + assert lib.FetchRectBottoms1() == 502 + # + p = ffi.addressof(lib, 'bottom') + assert ffi.typeof(p) == ffi.typeof("long *") + assert p[0] == 301 + p[0] += 1 + assert lib.FetchRectBottom() == 302 + p = ffi.addressof(lib, 'bottoms') + assert ffi.typeof(p) == ffi.typeof("long(*)[2]") + assert p[0] == lib.bottoms + # + py.test.raises(AttributeError, ffi.addressof, lib, 'unknown_var') + py.test.raises(AttributeError, ffi.addressof, lib, "FOOBAR") + +def test_defines__CFFI_(): + # Check that we define the macro _CFFI_ automatically. + # It should be done before including Python.h, so that PyPy's Python.h + # can check for it. + ffi = FFI() + ffi.cdef(""" + #define CORRECT 1 + """) + lib = verify(ffi, "test_defines__CFFI_", """ + #ifdef _CFFI_ + # define CORRECT 1 + #endif + """) + assert lib.CORRECT == 1 + +def test_unpack_args(): + ffi = FFI() + ffi.cdef("void foo0(void); void foo1(int); void foo2(int, int);") + lib = verify(ffi, "test_unpack_args", """ + void foo0(void) { } + void foo1(int x) { } + void foo2(int x, int y) { } + """) + assert 'foo0' in repr(lib.foo0) + assert 'foo1' in repr(lib.foo1) + assert 'foo2' in repr(lib.foo2) + lib.foo0() + lib.foo1(42) + lib.foo2(43, 44) + e1 = py.test.raises(TypeError, lib.foo0, 42) + e2 = py.test.raises(TypeError, lib.foo0, 43, 44) + e3 = py.test.raises(TypeError, lib.foo1) + e4 = py.test.raises(TypeError, lib.foo1, 43, 44) + e5 = py.test.raises(TypeError, lib.foo2) + e6 = py.test.raises(TypeError, lib.foo2, 42) + e7 = py.test.raises(TypeError, lib.foo2, 45, 46, 47) + assert str(e1.value) == "foo0() takes no arguments (1 given)" + assert str(e2.value) == "foo0() takes no arguments (2 given)" + assert str(e3.value) == "foo1() takes exactly one argument (0 given)" + assert str(e4.value) == "foo1() takes exactly one argument (2 given)" + assert str(e5.value) in ["foo2 expected 2 arguments, got 0", + "foo2() takes exactly 2 arguments (0 given)"] + assert str(e6.value) in ["foo2 expected 2 arguments, got 1", + "foo2() takes exactly 2 arguments (1 given)"] + assert str(e7.value) in ["foo2 expected 2 arguments, got 3", + "foo2() takes exactly 2 arguments (3 given)"] + +def test_address_of_function(): + ffi = FFI() + ffi.cdef("long myfunc(long x);") + lib = verify(ffi, "test_addressof_function", """ + char myfunc(char x) { return (char)(x + 42); } + """) + assert lib.myfunc(5) == 47 + assert lib.myfunc(0xABC05) == 47 + assert not isinstance(lib.myfunc, ffi.CData) + assert ffi.typeof(lib.myfunc) == ffi.typeof("long(*)(long)") + addr = ffi.addressof(lib, 'myfunc') + assert addr(5) == 47 + assert addr(0xABC05) == 47 + assert isinstance(addr, ffi.CData) + assert ffi.typeof(addr) == ffi.typeof("long(*)(long)") + +def test_address_of_function_with_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { int x; }; long myfunc(struct foo_s);") + lib = verify(ffi, "test_addressof_function_with_struct", """ + struct foo_s { int x; }; + char myfunc(struct foo_s input) { return (char)(input.x + 42); } + """) + s = ffi.new("struct foo_s *", [5])[0] + assert lib.myfunc(s) == 47 + assert not isinstance(lib.myfunc, ffi.CData) + assert ffi.typeof(lib.myfunc) == ffi.typeof("long(*)(struct foo_s)") + addr = ffi.addressof(lib, 'myfunc') + assert addr(s) == 47 + assert isinstance(addr, ffi.CData) + assert ffi.typeof(addr) == ffi.typeof("long(*)(struct foo_s)") + +def test_issue198(): + ffi = FFI() + ffi.cdef(""" + typedef struct{...;} opaque_t; + const opaque_t CONSTANT; + int toint(opaque_t); + """) + lib = verify(ffi, 'test_issue198', """ + typedef int opaque_t; + #define CONSTANT ((opaque_t)42) + static int toint(opaque_t o) { return o; } + """) + def random_stuff(): + pass + assert lib.toint(lib.CONSTANT) == 42 + random_stuff() + assert lib.toint(lib.CONSTANT) == 42 + +def test_constant_is_not_a_compiler_constant(): + ffi = FFI() + ffi.cdef("static const float almost_forty_two;") + lib = verify(ffi, 'test_constant_is_not_a_compiler_constant', """ + static float f(void) { return 42.25; } + #define almost_forty_two (f()) + """) + assert lib.almost_forty_two == 42.25 + +def test_constant_of_unknown_size(): + ffi = FFI() + ffi.cdef(""" + typedef ... opaque_t; + const opaque_t CONSTANT; + """) + lib = verify(ffi, 'test_constant_of_unknown_size', + "typedef int opaque_t;" + "const int CONSTANT = 42;") + e = py.test.raises(ffi.error, getattr, lib, 'CONSTANT') + assert str(e.value) == ("constant 'CONSTANT' is of " + "type 'opaque_t', whose size is not known") + +def test_variable_of_unknown_size(): + ffi = FFI() + ffi.cdef(""" + typedef ... opaque_t; + opaque_t globvar; + """) + lib = verify(ffi, 'test_variable_of_unknown_size', """ + typedef char opaque_t[6]; + opaque_t globvar = "hello"; + """) + # can't read or write it at all + e = py.test.raises(TypeError, getattr, lib, 'globvar') + assert str(e.value) in ["cdata 'opaque_t' is opaque", + "'opaque_t' is opaque or not completed yet"] #pypy + e = py.test.raises(TypeError, setattr, lib, 'globvar', []) + assert str(e.value) in ["'opaque_t' is opaque", + "'opaque_t' is opaque or not completed yet"] #pypy + # but we can get its address + p = ffi.addressof(lib, 'globvar') + assert ffi.typeof(p) == ffi.typeof('opaque_t *') + assert ffi.string(ffi.cast("char *", p), 8) == b"hello" + +def test_constant_of_value_unknown_to_the_compiler(): + extra_c_source = udir.join( + 'extra_test_constant_of_value_unknown_to_the_compiler.c') + extra_c_source.write('const int external_foo = 42;\n') + ffi = FFI() + ffi.cdef("const int external_foo;") + lib = verify(ffi, 'test_constant_of_value_unknown_to_the_compiler', """ + extern const int external_foo; + """, sources=[str(extra_c_source)]) + assert lib.external_foo == 42 + +def test_dotdot_in_source_file_names(): + extra_c_source = udir.join( + 'extra_test_dotdot_in_source_file_names.c') + extra_c_source.write('const int external_foo = 42;\n') + ffi = FFI() + ffi.cdef("const int external_foo;") + lib = verify(ffi, 'test_dotdot_in_source_file_names', """ + extern const int external_foo; + """, sources=[os.path.join(os.path.dirname(str(extra_c_source)), + 'foobar', '..', + os.path.basename(str(extra_c_source)))]) + assert lib.external_foo == 42 + +def test_call_with_incomplete_structs(): + ffi = FFI() + ffi.cdef("typedef struct {...;} foo_t; " + "foo_t myglob; " + "foo_t increment(foo_t s); " + "double getx(foo_t s);") + lib = verify(ffi, 'test_call_with_incomplete_structs', """ + typedef double foo_t; + double myglob = 42.5; + double getx(double x) { return x; } + double increment(double x) { return x + 1; } + """) + assert lib.getx(lib.myglob) == 42.5 + assert lib.getx(lib.increment(lib.myglob)) == 43.5 + +def test_struct_array_guess_length_2(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[...][...]; };") + lib = verify(ffi, 'test_struct_array_guess_length_2', + "struct foo_s { int x; int a[5][8]; int y; };") + assert ffi.sizeof('struct foo_s') == 42 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.typeof(s.a) == ffi.typeof("int[5][8]") + assert ffi.sizeof(s.a) == 40 * ffi.sizeof('int') + assert s.a[4][7] == 0 + py.test.raises(IndexError, 's.a[4][8]') + py.test.raises(IndexError, 's.a[5][0]') + assert ffi.typeof(s.a) == ffi.typeof("int[5][8]") + assert ffi.typeof(s.a[0]) == ffi.typeof("int[8]") + +def test_struct_array_guess_length_3(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[][...]; };") + lib = verify(ffi, 'test_struct_array_guess_length_3', + "struct foo_s { int x; int a[5][7]; int y; };") + assert ffi.sizeof('struct foo_s') == 37 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.typeof(s.a) == ffi.typeof("int[][7]") + assert s.a[4][6] == 0 + py.test.raises(IndexError, 's.a[4][7]') + assert ffi.typeof(s.a[0]) == ffi.typeof("int[7]") + +def test_global_var_array_2(): + ffi = FFI() + ffi.cdef("int a[...][...];") + lib = verify(ffi, 'test_global_var_array_2', 'int a[10][8];') + lib.a[9][7] = 123456 + assert lib.a[9][7] == 123456 + py.test.raises(IndexError, 'lib.a[0][8]') + py.test.raises(IndexError, 'lib.a[10][0]') + assert ffi.typeof(lib.a) == ffi.typeof("int[10][8]") + assert ffi.typeof(lib.a[0]) == ffi.typeof("int[8]") + +def test_global_var_array_3(): + ffi = FFI() + ffi.cdef("int a[][...];") + lib = verify(ffi, 'test_global_var_array_3', 'int a[10][8];') + lib.a[9][7] = 123456 + assert lib.a[9][7] == 123456 + py.test.raises(IndexError, 'lib.a[0][8]') + assert ffi.typeof(lib.a) == ffi.typeof("int(*)[8]") + assert ffi.typeof(lib.a[0]) == ffi.typeof("int[8]") + +def test_global_var_array_4(): + ffi = FFI() + ffi.cdef("int a[10][...];") + lib = verify(ffi, 'test_global_var_array_4', 'int a[10][8];') + lib.a[9][7] = 123456 + assert lib.a[9][7] == 123456 + py.test.raises(IndexError, 'lib.a[0][8]') + py.test.raises(IndexError, 'lib.a[10][8]') + assert ffi.typeof(lib.a) == ffi.typeof("int[10][8]") + assert ffi.typeof(lib.a[0]) == ffi.typeof("int[8]") + +def test_some_integer_type(): + ffi = FFI() + ffi.cdef(""" + typedef int... foo_t; + typedef unsigned long... bar_t; + typedef struct { foo_t a, b; } mystruct_t; + foo_t foobar(bar_t, mystruct_t); + static const bar_t mu = -20; + static const foo_t nu = 20; + """) + lib = verify(ffi, 'test_some_integer_type', """ + typedef unsigned long long foo_t; + typedef short bar_t; + typedef struct { foo_t a, b; } mystruct_t; + static foo_t foobar(bar_t x, mystruct_t s) { + return (foo_t)x + s.a + s.b; + } + static const bar_t mu = -20; + static const foo_t nu = 20; + """) + assert ffi.sizeof("foo_t") == ffi.sizeof("unsigned long long") + assert ffi.sizeof("bar_t") == ffi.sizeof("short") + maxulonglong = 2 ** 64 - 1 + assert int(ffi.cast("foo_t", -1)) == maxulonglong + assert int(ffi.cast("bar_t", -1)) == -1 + assert lib.foobar(-1, [0, 0]) == maxulonglong + assert lib.foobar(2 ** 15 - 1, [0, 0]) == 2 ** 15 - 1 + assert lib.foobar(10, [20, 31]) == 61 + assert lib.foobar(0, [0, maxulonglong]) == maxulonglong + py.test.raises(OverflowError, lib.foobar, 2 ** 15, [0, 0]) + py.test.raises(OverflowError, lib.foobar, -(2 ** 15) - 1, [0, 0]) + py.test.raises(OverflowError, ffi.new, "mystruct_t *", [0, -1]) + assert lib.mu == -20 + assert lib.nu == 20 + +def test_some_float_type(): + ffi = FFI() + ffi.cdef(""" + typedef double... foo_t; + typedef float... bar_t; + foo_t sum(foo_t[]); + bar_t neg(bar_t); + """) + lib = verify(ffi, 'test_some_float_type', """ + typedef float foo_t; + static foo_t sum(foo_t x[]) { return x[0] + x[1]; } + typedef double bar_t; + static double neg(double x) { return -x; } + """) + assert lib.sum([40.0, 2.25]) == 42.25 + assert lib.sum([12.3, 45.6]) != 12.3 + 45.6 # precision loss + assert lib.neg(12.3) == -12.3 # no precision loss + assert ffi.sizeof("foo_t") == ffi.sizeof("float") + assert ffi.sizeof("bar_t") == ffi.sizeof("double") + +def test_some_float_invalid_1(): + ffi = FFI() + py.test.raises((FFIError, # with pycparser <= 2.17 + CDefError), # with pycparser >= 2.18 + ffi.cdef, "typedef long double... foo_t;") + +def test_some_float_invalid_2(): + ffi = FFI() + ffi.cdef("typedef double... foo_t; foo_t neg(foo_t);") + lib = verify(ffi, 'test_some_float_invalid_2', """ + typedef unsigned long foo_t; + foo_t neg(foo_t x) { return -x; } + """) + e = py.test.raises(ffi.error, getattr, lib, 'neg') + assert str(e.value) == ("primitive floating-point type with an unexpected " + "size (or not a float type at all)") + +def test_some_float_invalid_3(): + ffi = FFI() + ffi.cdef("typedef double... foo_t; foo_t neg(foo_t);") + lib = verify(ffi, 'test_some_float_invalid_3', """ + typedef long double foo_t; + foo_t neg(foo_t x) { return -x; } + """) + if ffi.sizeof("long double") == ffi.sizeof("double"): + assert lib.neg(12.3) == -12.3 + else: + e = py.test.raises(ffi.error, getattr, lib, 'neg') + assert str(e.value) == ("primitive floating-point type is " + "'long double', not supported for now with " + "the syntax 'typedef double... xxx;'") + +def test_issue200(): + ffi = FFI() + ffi.cdef(""" + typedef void (function_t)(void*); + void function(void *); + """) + lib = verify(ffi, 'test_issue200', """ + static void function(void *p) { (void)p; } + """) + ffi.typeof('function_t*') + lib.function(ffi.NULL) + # assert did not crash + +def test_alignment_of_longlong(): + ffi = FFI() + x1 = ffi.alignof('unsigned long long') + assert x1 in [4, 8] + ffi.cdef("struct foo_s { unsigned long long x; };") + lib = verify(ffi, 'test_alignment_of_longlong', + "struct foo_s { unsigned long long x; };") + assert ffi.alignof('unsigned long long') == x1 + assert ffi.alignof('struct foo_s') == x1 + +def test_import_from_lib(): + ffi = FFI() + ffi.cdef("int mybar(int); int myvar;\n#define MYFOO ...") + lib = verify(ffi, 'test_import_from_lib', + "#define MYFOO 42\n" + "static int mybar(int x) { return x + 1; }\n" + "static int myvar = -5;") + assert sys.modules['_CFFI_test_import_from_lib'].lib is lib + assert sys.modules['_CFFI_test_import_from_lib.lib'] is lib + from _CFFI_test_import_from_lib.lib import MYFOO + assert MYFOO == 42 + assert hasattr(lib, '__dict__') + assert lib.__all__ == ['MYFOO', 'mybar'] # but not 'myvar' + assert lib.__name__ == '_CFFI_test_import_from_lib.lib' + assert lib.__class__ is type(sys) # !! hack for help() + +def test_macro_var_callback(): + ffi = FFI() + ffi.cdef("int my_value; int *(*get_my_value)(void);") + lib = verify(ffi, 'test_macro_var_callback', + "int *(*get_my_value)(void);\n" + "#define my_value (*get_my_value())") + # + values = ffi.new("int[50]") + def it(): + for i in range(50): + yield i + it = it() + # + @ffi.callback("int *(*)(void)") + def get_my_value(): + for nextvalue in it: + return values + nextvalue + lib.get_my_value = get_my_value + # + values[0] = 41 + assert lib.my_value == 41 # [0] + p = ffi.addressof(lib, 'my_value') # [1] + assert p == values + 1 + assert p[-1] == 41 + assert p[+1] == 0 + lib.my_value = 42 # [2] + assert values[2] == 42 + assert p[-1] == 41 + assert p[+1] == 42 + # + # if get_my_value raises or returns nonsense, the exception is printed + # to stderr like with any callback, but then the C expression 'my_value' + # expand to '*NULL'. We assume here that '&my_value' will return NULL + # without segfaulting, and check for NULL when accessing the variable. + @ffi.callback("int *(*)(void)") + def get_my_value(): + raise LookupError + lib.get_my_value = get_my_value + py.test.raises(ffi.error, getattr, lib, 'my_value') + py.test.raises(ffi.error, setattr, lib, 'my_value', 50) + py.test.raises(ffi.error, ffi.addressof, lib, 'my_value') + @ffi.callback("int *(*)(void)") + def get_my_value(): + return "hello" + lib.get_my_value = get_my_value + py.test.raises(ffi.error, getattr, lib, 'my_value') + e = py.test.raises(ffi.error, setattr, lib, 'my_value', 50) + assert str(e.value) == "global variable 'my_value' is at address NULL" + +def test_const_fields(): + ffi = FFI() + ffi.cdef("""struct foo_s { const int a; void *const b; };""") + lib = verify(ffi, 'test_const_fields', """ + struct foo_s { const int a; void *const b; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'a' + assert foo_s.fields[0][1].type is ffi.typeof("int") + assert foo_s.fields[1][0] == 'b' + assert foo_s.fields[1][1].type is ffi.typeof("void *") + +def test_restrict_fields(): + ffi = FFI() + ffi.cdef("""struct foo_s { void * restrict b; };""") + lib = verify(ffi, 'test_restrict_fields', """ + struct foo_s { void * __restrict b; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'b' + assert foo_s.fields[0][1].type is ffi.typeof("void *") + +def test_volatile_fields(): + ffi = FFI() + ffi.cdef("""struct foo_s { void * volatile b; };""") + lib = verify(ffi, 'test_volatile_fields', """ + struct foo_s { void * volatile b; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'b' + assert foo_s.fields[0][1].type is ffi.typeof("void *") + +def test_const_array_fields(): + ffi = FFI() + ffi.cdef("""struct foo_s { const int a[4]; };""") + lib = verify(ffi, 'test_const_array_fields', """ + struct foo_s { const int a[4]; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'a' + assert foo_s.fields[0][1].type is ffi.typeof("int[4]") + +def test_const_array_fields_varlength(): + ffi = FFI() + ffi.cdef("""struct foo_s { const int a[]; ...; };""") + lib = verify(ffi, 'test_const_array_fields_varlength', """ + struct foo_s { const int a[4]; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'a' + assert foo_s.fields[0][1].type is ffi.typeof("int[]") + +def test_const_array_fields_unknownlength(): + ffi = FFI() + ffi.cdef("""struct foo_s { const int a[...]; ...; };""") + lib = verify(ffi, 'test_const_array_fields_unknownlength', """ + struct foo_s { const int a[4]; };""") + foo_s = ffi.typeof("struct foo_s") + assert foo_s.fields[0][0] == 'a' + assert foo_s.fields[0][1].type is ffi.typeof("int[4]") + +def test_const_function_args(): + ffi = FFI() + ffi.cdef("""int foobar(const int a, const int *b, const int c[]);""") + lib = verify(ffi, 'test_const_function_args', """ + int foobar(const int a, const int *b, const int c[]) { + return a + *b + *c; + } + """) + assert lib.foobar(100, ffi.new("int *", 40), ffi.new("int *", 2)) == 142 + +def test_const_function_type_args(): + ffi = FFI() + ffi.cdef("""int (*foobar)(const int a, const int *b, const int c[]);""") + lib = verify(ffi, 'test_const_function_type_args', """ + int (*foobar)(const int a, const int *b, const int c[]); + """) + t = ffi.typeof(lib.foobar) + assert t.args[0] is ffi.typeof("int") + assert t.args[1] is ffi.typeof("int *") + assert t.args[2] is ffi.typeof("int *") + +def test_const_constant(): + ffi = FFI() + ffi.cdef("""struct foo_s { int x,y; }; const struct foo_s myfoo;""") + lib = verify(ffi, 'test_const_constant', """ + struct foo_s { int x,y; }; const struct foo_s myfoo = { 40, 2 }; + """) + assert lib.myfoo.x == 40 + assert lib.myfoo.y == 2 + +def test_const_via_typedef(): + ffi = FFI() + ffi.cdef("""typedef const int const_t; const_t aaa;""") + lib = verify(ffi, 'test_const_via_typedef', """ + typedef const int const_t; + #define aaa 42 + """) + assert lib.aaa == 42 + py.test.raises(AttributeError, "lib.aaa = 43") + +def test_win32_calling_convention_0(): + ffi = FFI() + ffi.cdef(""" + int call1(int(__cdecl *cb)(int)); + int (*const call2)(int(__stdcall *cb)(int)); + """) + lib = verify(ffi, 'test_win32_calling_convention_0', r""" + #ifndef _MSC_VER + # define __stdcall /* nothing */ + #endif + int call1(int(*cb)(int)) { + int i, result = 0; + //printf("call1: cb = %p\n", cb); + for (i = 0; i < 1000; i++) + result += cb(i); + //printf("result = %d\n", result); + return result; + } + int call2(int(__stdcall *cb)(int)) { + int i, result = 0; + //printf("call2: cb = %p\n", cb); + for (i = 0; i < 1000; i++) + result += cb(-i); + //printf("result = %d\n", result); + return result; + } + """) + @ffi.callback("int(int)") + def cb1(x): + return x * 2 + @ffi.callback("int __stdcall(int)") + def cb2(x): + return x * 3 + res = lib.call1(cb1) + assert res == 500*999*2 + assert res == ffi.addressof(lib, 'call1')(cb1) + res = lib.call2(cb2) + assert res == -500*999*3 + assert res == ffi.addressof(lib, 'call2')(cb2) + if sys.platform == 'win32' and not sys.maxsize > 2**32: + assert '__stdcall' in str(ffi.typeof(cb2)) + assert '__stdcall' not in str(ffi.typeof(cb1)) + py.test.raises(TypeError, lib.call1, cb2) + py.test.raises(TypeError, lib.call2, cb1) + else: + assert '__stdcall' not in str(ffi.typeof(cb2)) + assert ffi.typeof(cb2) is ffi.typeof(cb1) + +def test_win32_calling_convention_1(): + ffi = FFI() + ffi.cdef(""" + int __cdecl call1(int(__cdecl *cb)(int)); + int __stdcall call2(int(__stdcall *cb)(int)); + int (__cdecl *const cb1)(int); + int (__stdcall *const cb2)(int); + """) + lib = verify(ffi, 'test_win32_calling_convention_1', r""" + #ifndef _MSC_VER + # define __cdecl + # define __stdcall + #endif + int __cdecl cb1(int x) { return x * 2; } + int __stdcall cb2(int x) { return x * 3; } + + int __cdecl call1(int(__cdecl *cb)(int)) { + int i, result = 0; + //printf("here1\n"); + //printf("cb = %p, cb1 = %p\n", cb, (void *)cb1); + for (i = 0; i < 1000; i++) + result += cb(i); + //printf("result = %d\n", result); + return result; + } + int __stdcall call2(int(__stdcall *cb)(int)) { + int i, result = 0; + //printf("here1\n"); + //printf("cb = %p, cb2 = %p\n", cb, (void *)cb2); + for (i = 0; i < 1000; i++) + result += cb(-i); + //printf("result = %d\n", result); + return result; + } + """) + #print '<<< cb1 =', ffi.addressof(lib, 'cb1') + ptr_call1 = ffi.addressof(lib, 'call1') + assert lib.call1(ffi.addressof(lib, 'cb1')) == 500*999*2 + assert ptr_call1(ffi.addressof(lib, 'cb1')) == 500*999*2 + #print '<<< cb2 =', ffi.addressof(lib, 'cb2') + ptr_call2 = ffi.addressof(lib, 'call2') + assert lib.call2(ffi.addressof(lib, 'cb2')) == -500*999*3 + assert ptr_call2(ffi.addressof(lib, 'cb2')) == -500*999*3 + #print '<<< done' + +def test_win32_calling_convention_2(): + # any mistake in the declaration of plain function (including the + # precise argument types and, here, the calling convention) are + # automatically corrected. But this does not apply to the 'cb' + # function pointer argument. + ffi = FFI() + ffi.cdef(""" + int __stdcall call1(int(__cdecl *cb)(int)); + int __cdecl call2(int(__stdcall *cb)(int)); + int (__cdecl *const cb1)(int); + int (__stdcall *const cb2)(int); + """) + lib = verify(ffi, 'test_win32_calling_convention_2', """ + #ifndef _MSC_VER + # define __cdecl + # define __stdcall + #endif + int __cdecl call1(int(__cdecl *cb)(int)) { + int i, result = 0; + for (i = 0; i < 1000; i++) + result += cb(i); + return result; + } + int __stdcall call2(int(__stdcall *cb)(int)) { + int i, result = 0; + for (i = 0; i < 1000; i++) + result += cb(-i); + return result; + } + int __cdecl cb1(int x) { return x * 2; } + int __stdcall cb2(int x) { return x * 3; } + """) + ptr_call1 = ffi.addressof(lib, 'call1') + ptr_call2 = ffi.addressof(lib, 'call2') + if sys.platform == 'win32' and not sys.maxsize > 2**32: + py.test.raises(TypeError, lib.call1, ffi.addressof(lib, 'cb2')) + py.test.raises(TypeError, ptr_call1, ffi.addressof(lib, 'cb2')) + py.test.raises(TypeError, lib.call2, ffi.addressof(lib, 'cb1')) + py.test.raises(TypeError, ptr_call2, ffi.addressof(lib, 'cb1')) + assert lib.call1(ffi.addressof(lib, 'cb1')) == 500*999*2 + assert ptr_call1(ffi.addressof(lib, 'cb1')) == 500*999*2 + assert lib.call2(ffi.addressof(lib, 'cb2')) == -500*999*3 + assert ptr_call2(ffi.addressof(lib, 'cb2')) == -500*999*3 + +def test_win32_calling_convention_3(): + ffi = FFI() + ffi.cdef(""" + struct point { int x, y; }; + + int (*const cb1)(struct point); + int (__stdcall *const cb2)(struct point); + + struct point __stdcall call1(int(*cb)(struct point)); + struct point call2(int(__stdcall *cb)(struct point)); + """) + lib = verify(ffi, 'test_win32_calling_convention_3', r""" + #ifndef _MSC_VER + # define __cdecl + # define __stdcall + #endif + struct point { int x, y; }; + int cb1(struct point pt) { return pt.x + 10 * pt.y; } + int __stdcall cb2(struct point pt) { return pt.x + 100 * pt.y; } + struct point __stdcall call1(int(__cdecl *cb)(struct point)) { + int i; + struct point result = { 0, 0 }; + //printf("here1\n"); + //printf("cb = %p, cb1 = %p\n", cb, (void *)cb1); + for (i = 0; i < 1000; i++) { + struct point p = { i, -i }; + int r = cb(p); + result.x += r; + result.y -= r; + } + return result; + } + struct point __cdecl call2(int(__stdcall *cb)(struct point)) { + int i; + struct point result = { 0, 0 }; + for (i = 0; i < 1000; i++) { + struct point p = { -i, i }; + int r = cb(p); + result.x += r; + result.y -= r; + } + return result; + } + """) + ptr_call1 = ffi.addressof(lib, 'call1') + ptr_call2 = ffi.addressof(lib, 'call2') + if sys.platform == 'win32' and not sys.maxsize > 2**32: + py.test.raises(TypeError, lib.call1, ffi.addressof(lib, 'cb2')) + py.test.raises(TypeError, ptr_call1, ffi.addressof(lib, 'cb2')) + py.test.raises(TypeError, lib.call2, ffi.addressof(lib, 'cb1')) + py.test.raises(TypeError, ptr_call2, ffi.addressof(lib, 'cb1')) + pt = lib.call1(ffi.addressof(lib, 'cb1')) + assert (pt.x, pt.y) == (-9*500*999, 9*500*999) + pt = ptr_call1(ffi.addressof(lib, 'cb1')) + assert (pt.x, pt.y) == (-9*500*999, 9*500*999) + pt = lib.call2(ffi.addressof(lib, 'cb2')) + assert (pt.x, pt.y) == (99*500*999, -99*500*999) + pt = ptr_call2(ffi.addressof(lib, 'cb2')) + assert (pt.x, pt.y) == (99*500*999, -99*500*999) + +def test_extern_python_1(): + import warnings + ffi = FFI() + with warnings.catch_warnings(record=True) as log: + ffi.cdef(""" + extern "Python" { + int bar(int, int); + void baz(int, int); + int bok(void); + void boz(void); + } + """) + assert len(log) == 0, "got a warning: %r" % (log,) + lib = verify(ffi, 'test_extern_python_1', """ + static void baz(int, int); /* forward */ + """) + assert ffi.typeof(lib.bar) == ffi.typeof("int(*)(int, int)") + with FdWriteCapture() as f: + res = lib.bar(4, 5) + assert res == 0 + assert f.getvalue() == ( + b"extern \"Python\": function _CFFI_test_extern_python_1.bar() called, " + b"but no code was attached " + b"to it yet with @ffi.def_extern(). Returning 0.\n") + + @ffi.def_extern("bar") + def my_bar(x, y): + seen.append(("Bar", x, y)) + return x * y + assert my_bar != lib.bar + seen = [] + res = lib.bar(6, 7) + assert seen == [("Bar", 6, 7)] + assert res == 42 + + def baz(x, y): + seen.append(("Baz", x, y)) + baz1 = ffi.def_extern()(baz) + assert baz1 is baz + seen = [] + baz(long(40), long(4)) + res = lib.baz(long(50), long(8)) + assert res is None + assert seen == [("Baz", 40, 4), ("Baz", 50, 8)] + assert type(seen[0][1]) is type(seen[0][2]) is long + assert type(seen[1][1]) is type(seen[1][2]) is int + + @ffi.def_extern(name="bok") + def bokk(): + seen.append("Bok") + return 42 + seen = [] + assert lib.bok() == 42 + assert seen == ["Bok"] + + @ffi.def_extern() + def boz(): + seen.append("Boz") + seen = [] + assert lib.boz() is None + assert seen == ["Boz"] + +def test_extern_python_bogus_name(): + ffi = FFI() + ffi.cdef("int abc;") + lib = verify(ffi, 'test_extern_python_bogus_name', "int abc;") + def fn(): + pass + py.test.raises(ffi.error, ffi.def_extern("unknown_name"), fn) + py.test.raises(ffi.error, ffi.def_extern("abc"), fn) + assert lib.abc == 0 + e = py.test.raises(ffi.error, ffi.def_extern("abc"), fn) + assert str(e.value) == ("ffi.def_extern('abc'): no 'extern \"Python\"' " + "function with this name") + e = py.test.raises(ffi.error, ffi.def_extern(), fn) + assert str(e.value) == ("ffi.def_extern('fn'): no 'extern \"Python\"' " + "function with this name") + # + py.test.raises(TypeError, ffi.def_extern(42), fn) + py.test.raises((TypeError, AttributeError), ffi.def_extern(), "foo") + class X: + pass + x = X() + x.__name__ = x + py.test.raises(TypeError, ffi.def_extern(), x) + +def test_extern_python_bogus_result_type(): + ffi = FFI() + ffi.cdef("""extern "Python" void bar(int);""") + lib = verify(ffi, 'test_extern_python_bogus_result_type', "") + # + @ffi.def_extern() + def bar(n): + return n * 10 + with StdErrCapture() as f: + res = lib.bar(321) + assert res is None + assert f.getvalue() == ( + "From cffi callback %r:\n" % (bar,) + + "Trying to convert the result back to C:\n" + "TypeError: callback with the return type 'void' must return None\n") + +def test_extern_python_redefine(): + ffi = FFI() + ffi.cdef("""extern "Python" int bar(int);""") + lib = verify(ffi, 'test_extern_python_redefine', "") + # + @ffi.def_extern() + def bar(n): + return n * 10 + assert lib.bar(42) == 420 + # + @ffi.def_extern() + def bar(n): + return -n + assert lib.bar(42) == -42 + +def test_extern_python_struct(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { int a, b, c; }; + extern "Python" int bar(int, struct foo_s, int); + extern "Python" { struct foo_s baz(int, int); + struct foo_s bok(void); } + """) + lib = verify(ffi, 'test_extern_python_struct', + "struct foo_s { int a, b, c; };") + # + @ffi.def_extern() + def bar(x, s, z): + return x + s.a + s.b + s.c + z + res = lib.bar(1000, [1001, 1002, 1004], 1008) + assert res == 5015 + # + @ffi.def_extern() + def baz(x, y): + return [x + y, x - y, x * y] + res = lib.baz(1000, 42) + assert res.a == 1042 + assert res.b == 958 + assert res.c == 42000 + # + @ffi.def_extern() + def bok(): + return [10, 20, 30] + res = lib.bok() + assert [res.a, res.b, res.c] == [10, 20, 30] + +def test_extern_python_long_double(): + ffi = FFI() + ffi.cdef(""" + extern "Python" int bar(int, long double, int); + extern "Python" long double baz(int, int); + extern "Python" long double bok(void); + """) + lib = verify(ffi, 'test_extern_python_long_double', "") + # + @ffi.def_extern() + def bar(x, l, z): + seen.append((x, l, z)) + return 6 + seen = [] + lib.bar(10, 3.5, 20) + expected = ffi.cast("long double", 3.5) + assert repr(seen) == repr([(10, expected, 20)]) + # + @ffi.def_extern() + def baz(x, z): + assert x == 10 and z == 20 + return expected + res = lib.baz(10, 20) + assert repr(res) == repr(expected) + # + @ffi.def_extern() + def bok(): + return expected + res = lib.bok() + assert repr(res) == repr(expected) + +def test_extern_python_signature(): + ffi = FFI() + lib = verify(ffi, 'test_extern_python_signature', "") + py.test.raises(TypeError, ffi.def_extern(425), None) + py.test.raises(TypeError, ffi.def_extern, 'a', 'b', 'c', 'd') + +def test_extern_python_errors(): + ffi = FFI() + ffi.cdef(""" + extern "Python" int bar(int); + """) + lib = verify(ffi, 'test_extern_python_errors', "") + + seen = [] + def oops(*args): + seen.append(args) + + @ffi.def_extern(onerror=oops) + def bar(x): + return x + "" + assert lib.bar(10) == 0 + + @ffi.def_extern(name="bar", onerror=oops, error=-66) + def bar2(x): + return x + "" + assert lib.bar(10) == -66 + + assert len(seen) == 2 + exc, val, tb = seen[0] + assert exc is TypeError + assert isinstance(val, TypeError) + assert tb.tb_frame.f_code.co_name == "bar" + exc, val, tb = seen[1] + assert exc is TypeError + assert isinstance(val, TypeError) + assert tb.tb_frame.f_code.co_name == "bar2" + # + # a case where 'onerror' is not callable + py.test.raises(TypeError, ffi.def_extern(name='bar', onerror=42), + lambda x: x) + +def test_extern_python_stdcall(): + ffi = FFI() + ffi.cdef(""" + extern "Python" int __stdcall foo(int); + extern "Python" int WINAPI bar(int); + int (__stdcall * mycb1)(int); + int indirect_call(int); + """) + lib = verify(ffi, 'test_extern_python_stdcall', """ + #ifndef _MSC_VER + # define __stdcall + #endif + static int (__stdcall * mycb1)(int); + static int indirect_call(int x) { + return mycb1(x); + } + """) + # + @ffi.def_extern() + def foo(x): + return x + 42 + @ffi.def_extern() + def bar(x): + return x + 43 + assert lib.foo(100) == 142 + assert lib.bar(100) == 143 + lib.mycb1 = lib.foo + assert lib.mycb1(200) == 242 + assert lib.indirect_call(300) == 342 + +def test_extern_python_plus_c(): + ffi = FFI() + ffi.cdef(""" + extern "Python+C" int foo(int); + extern "C +\tPython" int bar(int); + int call_me(int); + """) + lib = verify(ffi, 'test_extern_python_plus_c', """ + int foo(int); + #ifdef __GNUC__ + __attribute__((visibility("hidden"))) + #endif + int bar(int); + + static int call_me(int x) { + return foo(x) - bar(x); + } + """) + # + @ffi.def_extern() + def foo(x): + return x * 42 + @ffi.def_extern() + def bar(x): + return x * 63 + assert lib.foo(100) == 4200 + assert lib.bar(100) == 6300 + assert lib.call_me(100) == -2100 + +def test_introspect_function(): + ffi = FFI() + ffi.cdef("float f1(double);") + lib = verify(ffi, 'test_introspect_function', """ + float f1(double x) { return x; } + """) + assert dir(lib) == ['f1'] + FUNC = ffi.typeof(lib.f1) + assert FUNC.kind == 'function' + assert FUNC.args[0].cname == 'double' + assert FUNC.result.cname == 'float' + assert ffi.typeof(ffi.addressof(lib, 'f1')) is FUNC + +def test_introspect_global_var(): + ffi = FFI() + ffi.cdef("float g1;") + lib = verify(ffi, 'test_introspect_global_var', """ + float g1; + """) + assert dir(lib) == ['g1'] + FLOATPTR = ffi.typeof(ffi.addressof(lib, 'g1')) + assert FLOATPTR.kind == 'pointer' + assert FLOATPTR.item.cname == 'float' + +def test_introspect_global_var_array(): + ffi = FFI() + ffi.cdef("float g1[100];") + lib = verify(ffi, 'test_introspect_global_var_array', """ + float g1[100]; + """) + assert dir(lib) == ['g1'] + FLOATARRAYPTR = ffi.typeof(ffi.addressof(lib, 'g1')) + assert FLOATARRAYPTR.kind == 'pointer' + assert FLOATARRAYPTR.item.kind == 'array' + assert FLOATARRAYPTR.item.length == 100 + assert ffi.typeof(lib.g1) is FLOATARRAYPTR.item + +def test_introspect_integer_const(): + ffi = FFI() + ffi.cdef("#define FOO 42") + lib = verify(ffi, 'test_introspect_integer_const', """ + #define FOO 42 + """) + assert dir(lib) == ['FOO'] + assert lib.FOO == ffi.integer_const('FOO') == 42 + +def test_introspect_typedef(): + ffi = FFI() + ffi.cdef("typedef int foo_t;") + lib = verify(ffi, 'test_introspect_typedef', """ + typedef int foo_t; + """) + assert ffi.list_types() == (['foo_t'], [], []) + assert ffi.typeof('foo_t').kind == 'primitive' + assert ffi.typeof('foo_t').cname == 'int' + +def test_introspect_typedef_multiple(): + ffi = FFI() + ffi.cdef("typedef signed char a_t, c_t, g_t, b_t;") + lib = verify(ffi, 'test_introspect_typedef_multiple', """ + typedef signed char a_t, c_t, g_t, b_t; + """) + assert ffi.list_types() == (['a_t', 'b_t', 'c_t', 'g_t'], [], []) + +def test_introspect_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { int a; };") + lib = verify(ffi, 'test_introspect_struct', """ + struct foo_s { int a; }; + """) + assert ffi.list_types() == ([], ['foo_s'], []) + assert ffi.typeof('struct foo_s').kind == 'struct' + assert ffi.typeof('struct foo_s').cname == 'struct foo_s' + +def test_introspect_union(): + ffi = FFI() + ffi.cdef("union foo_s { int a; };") + lib = verify(ffi, 'test_introspect_union', """ + union foo_s { int a; }; + """) + assert ffi.list_types() == ([], [], ['foo_s']) + assert ffi.typeof('union foo_s').kind == 'union' + assert ffi.typeof('union foo_s').cname == 'union foo_s' + +def test_introspect_struct_and_typedef(): + ffi = FFI() + ffi.cdef("typedef struct { int a; } foo_t;") + lib = verify(ffi, 'test_introspect_struct_and_typedef', """ + typedef struct { int a; } foo_t; + """) + assert ffi.list_types() == (['foo_t'], [], []) + assert ffi.typeof('foo_t').kind == 'struct' + assert ffi.typeof('foo_t').cname == 'foo_t' + +def test_introspect_included_type(): + SOURCE = """ + typedef signed char schar_t; + struct sint_t { int x; }; + """ + ffi1 = FFI() + ffi1.cdef(SOURCE) + ffi2 = FFI() + ffi2.include(ffi1) + verify(ffi1, "test_introspect_included_type_parent", SOURCE) + verify(ffi2, "test_introspect_included_type", SOURCE) + assert ffi1.list_types() == ffi2.list_types() == ( + ['schar_t'], ['sint_t'], []) + +def test_introspect_order(): + ffi = FFI() + ffi.cdef("union CFFIaaa { int a; }; typedef struct CFFIccc { int a; } CFFIb;") + ffi.cdef("union CFFIg { int a; }; typedef struct CFFIcc { int a; } CFFIbbb;") + ffi.cdef("union CFFIaa { int a; }; typedef struct CFFIa { int a; } CFFIbb;") + verify(ffi, "test_introspect_order", """ + union CFFIaaa { int a; }; typedef struct CFFIccc { int a; } CFFIb; + union CFFIg { int a; }; typedef struct CFFIcc { int a; } CFFIbbb; + union CFFIaa { int a; }; typedef struct CFFIa { int a; } CFFIbb; + """) + assert ffi.list_types() == (['CFFIb', 'CFFIbb', 'CFFIbbb'], + ['CFFIa', 'CFFIcc', 'CFFIccc'], + ['CFFIaa', 'CFFIaaa', 'CFFIg']) + +def test_bool_in_cpp(): + # this works when compiled as C, but in cffi < 1.7 it fails as C++ + ffi = FFI() + ffi.cdef("bool f(void);") + lib = verify(ffi, "test_bool_in_cpp", "char f(void) { return 2; }") + assert lib.f() is True + +def test_bool_in_cpp_2(): + ffi = FFI() + ffi.cdef('int add(int a, int b);') + lib = verify(ffi, "test_bool_bug_cpp", ''' + typedef bool _Bool; /* there is a Windows header with this line */ + int add(int a, int b) + { + return a + b; + }''', source_extension='.cpp') + c = lib.add(2, 3) + assert c == 5 + +def test_struct_field_opaque(): + ffi = FFI() + ffi.cdef("struct a { struct b b; };") + e = py.test.raises(TypeError, verify, + ffi, "test_struct_field_opaque", "?") + assert str(e.value) == ("struct a: field 'a.b' is of an opaque" + " type (not declared in cdef())") + ffi = FFI() + ffi.cdef("struct a { struct b b[2]; };") + e = py.test.raises(TypeError, verify, + ffi, "test_struct_field_opaque", "?") + assert str(e.value) == ("struct a: field 'a.b' is of an opaque" + " type (not declared in cdef())") + ffi = FFI() + ffi.cdef("struct a { struct b b[]; };") + e = py.test.raises(TypeError, verify, + ffi, "test_struct_field_opaque", "?") + assert str(e.value) == ("struct a: field 'a.b' is of an opaque" + " type (not declared in cdef())") + +def test_function_arg_opaque(): + py.test.skip("can currently declare a function with an opaque struct " + "as argument, but AFAICT it's impossible to call it later") + +def test_function_returns_opaque(): + ffi = FFI() + ffi.cdef("struct a foo(int);") + e = py.test.raises(TypeError, verify, + ffi, "test_function_returns_opaque", "?") + assert str(e.value) == ("function foo: 'struct a' is used as result type," + " but is opaque") + +def test_function_returns_union(): + ffi = FFI() + ffi.cdef("union u1 { int a, b; }; union u1 f1(int);") + lib = verify(ffi, "test_function_returns_union", """ + union u1 { int a, b; }; + static union u1 f1(int x) { union u1 u; u.b = x; return u; } + """) + assert lib.f1(51).a == 51 + +def test_function_returns_partial_struct(): + ffi = FFI() + ffi.cdef("struct aaa { int a; ...; }; struct aaa f1(int);") + lib = verify(ffi, "test_function_returns_partial_struct", """ + struct aaa { int b, a, c; }; + static struct aaa f1(int x) { struct aaa s = {0}; s.a = x; return s; } + """) + assert lib.f1(52).a == 52 + +def test_function_returns_float_complex(): + if sys.platform == 'win32': + py.test.skip("MSVC may not support _Complex") + ffi = FFI() + ffi.cdef("float _Complex f1(float a, float b);"); + lib = verify(ffi, "test_function_returns_float_complex", """ + #include + static float _Complex f1(float a, float b) { return a + I*2.0*b; } + """, no_cpp=True) # fails on some systems with C++ + result = lib.f1(1.25, 5.1) + assert type(result) == complex + assert result.real == 1.25 # exact + assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact + +def test_function_returns_double_complex(): + if sys.platform == 'win32': + py.test.skip("MSVC may not support _Complex") + ffi = FFI() + ffi.cdef("double _Complex f1(double a, double b);"); + lib = verify(ffi, "test_function_returns_double_complex", """ + #include + static double _Complex f1(double a, double b) { return a + I*2.0*b; } + """, no_cpp=True) # fails on some systems with C++ + result = lib.f1(1.25, 5.1) + assert type(result) == complex + assert result.real == 1.25 # exact + assert result.imag == 2*5.1 # exact + +def test_function_argument_float_complex(): + if sys.platform == 'win32': + py.test.skip("MSVC may not support _Complex") + ffi = FFI() + ffi.cdef("float f1(float _Complex x);"); + lib = verify(ffi, "test_function_argument_float_complex", """ + #include + static float f1(float _Complex x) { return cabsf(x); } + """, no_cpp=True) # fails on some systems with C++ + x = complex(12.34, 56.78) + result = lib.f1(x) + assert abs(result - abs(x)) < 1e-5 + +def test_function_argument_double_complex(): + if sys.platform == 'win32': + py.test.skip("MSVC may not support _Complex") + ffi = FFI() + ffi.cdef("double f1(double _Complex);"); + lib = verify(ffi, "test_function_argument_double_complex", """ + #include + static double f1(double _Complex x) { return cabs(x); } + """, no_cpp=True) # fails on some systems with C++ + x = complex(12.34, 56.78) + result = lib.f1(x) + assert abs(result - abs(x)) < 1e-11 + +def test_typedef_array_dotdotdot(): + ffi = FFI() + ffi.cdef(""" + typedef int foo_t[...], bar_t[...]; + int gv[...]; + typedef int mat_t[...][...]; + typedef int vmat_t[][...]; + """) + lib = verify(ffi, "test_typedef_array_dotdotdot", """ + typedef int foo_t[50], bar_t[50]; + int gv[23]; + typedef int mat_t[6][7]; + typedef int vmat_t[][8]; + """) + assert ffi.sizeof("foo_t") == 50 * ffi.sizeof("int") + assert ffi.sizeof("bar_t") == 50 * ffi.sizeof("int") + assert len(ffi.new("foo_t")) == 50 + assert len(ffi.new("bar_t")) == 50 + assert ffi.sizeof(lib.gv) == 23 * ffi.sizeof("int") + assert ffi.sizeof("mat_t") == 6 * 7 * ffi.sizeof("int") + assert len(ffi.new("mat_t")) == 6 + assert len(ffi.new("mat_t")[3]) == 7 + py.test.raises(ffi.error, ffi.sizeof, "vmat_t") + p = ffi.new("vmat_t", 4) + assert ffi.sizeof(p[3]) == 8 * ffi.sizeof("int") + +def test_call_with_custom_field_pos(): + ffi = FFI() + ffi.cdef(""" + struct foo { int x; ...; }; + struct foo f(void); + struct foo g(int, ...); + """) + lib = verify(ffi, "test_call_with_custom_field_pos", """ + struct foo { int y, x; }; + struct foo f(void) { + struct foo s = { 40, 200 }; + return s; + } + struct foo g(int a, ...) { return f(); } + """) + assert lib.f().x == 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_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, ...) { return f(); } + """) + 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() + ffi.cdef(""" + struct foo { int x:5; }; + struct foo f(void); + struct foo g(int, ...); + """) + lib = verify(ffi, "test_call_with_bitfield", """ + struct foo { int x:5; }; + struct foo f(void) { + struct foo s = { 11 }; + return s; + } + struct foo g(int a, ...) { return f(); } + """) + assert lib.f().x == 11 + e = py.test.raises(NotImplementedError, lib.g, 0) + 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(): + if sys.platform == 'win32': + py.test.skip("zero-length field not supported by MSVC") + ffi = FFI() + ffi.cdef(""" + struct foo { int a; int x[0]; }; + struct foo f(void); + struct foo g(int, ...); + """) + lib = verify(ffi, "test_call_with_zero_length_field", """ + struct foo { int a; int x[0]; }; + struct foo f(void) { + struct foo s = { 42 }; + return s; + } + struct foo g(int a, ...) { return f(); } + """) + assert lib.f().a == 42 + e = py.test.raises(NotImplementedError, lib.g, 0) + 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() + ffi.cdef(""" + union foo { int a; char b; }; + union foo f(void); + union foo g(int, ...); + """) + lib = verify(ffi, "test_call_with_union", """ + union foo { int a; char b; }; + union foo f(void) { + union foo s = { 42 }; + return s; + } + union foo g(int a, ...) { return f(); } + """) + assert lib.f().a == 42 + e = py.test.raises(NotImplementedError, lib.g, 0) + 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': + py.test.skip("needs a GCC extension") + ffi = FFI() + ffi.cdef(""" + struct foo { char y; int x; }; + struct foo f(void); + struct foo g(int, ...); + """, packed=True) + lib = verify(ffi, "test_call_with_packed_struct", """ + struct foo { char y; int x; } __attribute__((packed)); + struct foo f(void) { + struct foo s = { 40, 200 }; + return s; + } + struct foo g(int a, ...) { + struct foo s = { 41, 201 }; + return s; + } + """) + assert ord(lib.f().y) == 40 + assert lib.f().x == 200 + e = py.test.raises(NotImplementedError, lib.g, 0) + 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)") + +def test_pack_not_supported(): + ffi = FFI() + ffi.cdef("""struct foo { char y; int x; };""", pack=2) + py.test.raises(NotImplementedError, verify, + ffi, "test_pack_not_supported", "") + +def test_gcc_visibility_hidden(): + if sys.platform == 'win32': + py.test.skip("test for gcc/clang") + ffi = FFI() + ffi.cdef(""" + int f(int); + """) + lib = verify(ffi, "test_gcc_visibility_hidden", """ + int f(int a) { return a + 40; } + """, extra_compile_args=['-fvisibility=hidden']) + assert lib.f(2) == 42 + +def test_override_default_definition(): + ffi = FFI() + ffi.cdef("typedef long int16_t, char16_t;") + lib = verify(ffi, "test_override_default_definition", "") + assert ffi.typeof("int16_t") is ffi.typeof("char16_t") is ffi.typeof("long") + +def test_char16_char32_type(no_cpp=False): + if no_cpp is False and sys.platform == "win32": + py.test.skip("aaaaaaa why do modern MSVC compilers still define " + "a very old __cplusplus value") + ffi = FFI() + ffi.cdef(""" + char16_t foo_2bytes(char16_t); + char32_t foo_4bytes(char32_t); + """) + lib = verify(ffi, "test_char16_char32_type" + no_cpp * "_nocpp", """ + #if !defined(__cplusplus) || (!defined(_LIBCPP_VERSION) && __cplusplus < 201103L) + typedef uint_least16_t char16_t; + typedef uint_least32_t char32_t; + #endif + + char16_t foo_2bytes(char16_t a) { return (char16_t)(a + 42); } + char32_t foo_4bytes(char32_t a) { return (char32_t)(a + 42); } + """, no_cpp=no_cpp) + assert lib.foo_2bytes(u+'\u1234') == u+'\u125e' + assert lib.foo_4bytes(u+'\u1234') == u+'\u125e' + assert lib.foo_4bytes(u+'\U00012345') == u+'\U0001236f' + py.test.raises(TypeError, lib.foo_2bytes, u+'\U00012345') + py.test.raises(TypeError, lib.foo_2bytes, 1234) + py.test.raises(TypeError, lib.foo_4bytes, 1234) + +def test_char16_char32_plain_c(): + test_char16_char32_type(no_cpp=True) + +def test_loader_spec(): + ffi = FFI() + lib = verify(ffi, "test_loader_spec", "") + if sys.version_info < (3,): + assert not hasattr(lib, '__loader__') + assert not hasattr(lib, '__spec__') + else: + assert lib.__loader__ is None + assert lib.__spec__ is None + +def test_realize_struct_error(): + ffi = FFI() + ffi.cdef("""typedef ... foo_t; struct foo_s { void (*x)(foo_t); };""") + lib = verify(ffi, "test_realize_struct_error", """ + typedef int foo_t; struct foo_s { void (*x)(foo_t); }; + """) + py.test.raises(TypeError, ffi.new, "struct foo_s *") diff --git a/testing/cffi1/test_unicode_literals.py b/testing/cffi1/test_unicode_literals.py new file mode 100644 index 0000000..e9825db --- /dev/null +++ b/testing/cffi1/test_unicode_literals.py @@ -0,0 +1,43 @@ +# +# ---------------------------------------------- +# WARNING, ALL LITERALS IN THIS FILE ARE UNICODE +# ---------------------------------------------- +# +from __future__ import unicode_literals +# +# +# +from _cffi_backend import FFI + + +def test_cast(): + ffi = FFI() + assert int(ffi.cast("int", 3.14)) == 3 # unicode literal + +def test_new(): + ffi = FFI() + assert ffi.new("int[]", [3, 4, 5])[2] == 5 # unicode literal + +def test_typeof(): + ffi = FFI() + tp = ffi.typeof("int[51]") # unicode literal + assert tp.length == 51 + +def test_sizeof(): + ffi = FFI() + assert ffi.sizeof("int[51]") == 51 * 4 # unicode literal + +def test_alignof(): + ffi = FFI() + assert ffi.alignof("int[51]") == 4 # unicode literal + +def test_getctype(): + ffi = FFI() + assert ffi.getctype("int**") == "int * *" # unicode literal + assert type(ffi.getctype("int**")) is str + +def test_callback(): + ffi = FFI() + cb = ffi.callback("int(int)", # unicode literal + lambda x: x + 42) + assert cb(5) == 47 diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py new file mode 100644 index 0000000..75f113d --- /dev/null +++ b/testing/cffi1/test_verify1.py @@ -0,0 +1,2363 @@ +import os, sys, math, py +from cffi import FFI, FFIError, VerificationError, VerificationMissing, model +from cffi import CDefError +from cffi import recompiler +from testing.support import * +from testing.support import _verify +import _cffi_backend + +lib_m = ['m'] +if sys.platform == 'win32': + #there is a small chance this fails on Mingw via environ $CC + import distutils.ccompiler + if distutils.ccompiler.get_default_compiler() == 'msvc': + lib_m = ['msvcrt'] + extra_compile_args = [] # no obvious -Werror equivalent on MSVC +else: + if (sys.platform == 'darwin' and + [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]): + # assume a standard clang or gcc + extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] + # special things for clang + extra_compile_args.append('-Qunused-arguments') + else: + # assume a standard gcc + extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion'] + +class FFI(FFI): + error = _cffi_backend.FFI.error + _extra_compile_args = extra_compile_args + _verify_counter = 0 + + def verify(self, preamble='', *args, **kwds): + # HACK to reuse the tests from ../cffi0/test_verify.py + FFI._verify_counter += 1 + module_name = 'verify%d' % FFI._verify_counter + try: + del self._assigned_source + except AttributeError: + pass + self.set_source(module_name, preamble) + return _verify(self, module_name, preamble, *args, + extra_compile_args=self._extra_compile_args, **kwds) + +class FFI_warnings_not_error(FFI): + _extra_compile_args = [] + + +def test_missing_function(ffi=None): + # uses the FFI hacked above with '-Werror' + if ffi is None: + ffi = FFI() + ffi.cdef("void some_completely_unknown_function();") + try: + lib = ffi.verify() + except (VerificationError, OSError, ImportError): + pass # expected case: we get a VerificationError + else: + # but depending on compiler and loader details, maybe + # 'lib' could actually be imported but will fail if we + # actually try to call the unknown function... Hard + # to test anything more. + pass + +def test_missing_function_import_error(): + # uses the original FFI that just gives a warning during compilation + test_missing_function(ffi=FFI_warnings_not_error()) + +def test_simple_case(): + ffi = FFI() + ffi.cdef("double sin(double x);") + lib = ffi.verify('#include ', libraries=lib_m) + assert lib.sin(1.23) == math.sin(1.23) + +def _Wconversion(cdef, source, **kargs): + if sys.platform in ('win32', 'darwin'): + py.test.skip("needs GCC") + ffi = FFI() + ffi.cdef(cdef) + py.test.raises(VerificationError, ffi.verify, source, **kargs) + extra_compile_args_orig = extra_compile_args[:] + extra_compile_args.remove('-Wconversion') + try: + lib = ffi.verify(source, **kargs) + finally: + extra_compile_args[:] = extra_compile_args_orig + return lib + +def test_Wconversion_unsigned(): + _Wconversion("unsigned foo(void);", + "int foo(void) { return -1;}") + +def test_Wconversion_integer(): + _Wconversion("short foo(void);", + "long long foo(void) { return 1<", libraries=lib_m) + res = lib.sin(1.23) + assert res != math.sin(1.23) # not exact, because of double->float + assert abs(res - math.sin(1.23)) < 1E-5 + +def test_Wconversion_float2int(): + _Wconversion("int sinf(float);", + "#include ", libraries=lib_m) + +def test_Wconversion_double2int(): + _Wconversion("int sin(double);", + "#include ", libraries=lib_m) + +def test_rounding_1(): + ffi = FFI() + ffi.cdef("double sinf(float x);") + lib = ffi.verify('#include ', libraries=lib_m) + res = lib.sinf(1.23) + assert res != math.sin(1.23) # not exact, because of double->float + assert abs(res - math.sin(1.23)) < 1E-5 + +def test_rounding_2(): + ffi = FFI() + ffi.cdef("double sin(float x);") + lib = ffi.verify('#include ', libraries=lib_m) + res = lib.sin(1.23) + assert res != math.sin(1.23) # not exact, because of double->float + assert abs(res - math.sin(1.23)) < 1E-5 + +def test_strlen_exact(): + ffi = FFI() + ffi.cdef("size_t strlen(const char *s);") + lib = ffi.verify("#include ") + assert lib.strlen(b"hi there!") == 9 + +def test_strlen_approximate(): + lib = _Wconversion("int strlen(char *s);", + "#include ") + assert lib.strlen(b"hi there!") == 9 + +def test_return_approximate(): + for typename in ['short', 'int', 'long', 'long long']: + ffi = FFI() + ffi.cdef("%s foo(signed char x);" % typename) + lib = ffi.verify("signed char foo(signed char x) { return x;}") + assert lib.foo(-128) == -128 + assert lib.foo(+127) == +127 + +def test_strlen_array_of_char(): + ffi = FFI() + ffi.cdef("size_t strlen(char[]);") + lib = ffi.verify("#include ") + assert lib.strlen(b"hello") == 5 + +def test_longdouble(): + ffi = FFI() + ffi.cdef("long double sinl(long double x);") + lib = ffi.verify('#include ', libraries=lib_m) + for input in [1.23, + ffi.cast("double", 1.23), + ffi.cast("long double", 1.23)]: + x = lib.sinl(input) + assert repr(x).startswith(" 0.1 + # Check the particular results on Intel + import platform + if (platform.machine().startswith('i386') or + platform.machine().startswith('i486') or + platform.machine().startswith('i586') or + platform.machine().startswith('i686') or + platform.machine().startswith('x86')): + assert abs(more_precise - 0.656769) < 0.001 + assert abs(less_precise - 3.99091) < 0.001 + else: + py.test.skip("don't know the very exact precision of 'long double'") + + +all_primitive_types = model.PrimitiveType.ALL_PRIMITIVE_TYPES +if sys.platform == 'win32': + all_primitive_types = all_primitive_types.copy() + del all_primitive_types['ssize_t'] +all_integer_types = sorted(tp for tp in all_primitive_types + if all_primitive_types[tp] == 'i') +all_float_types = sorted(tp for tp in all_primitive_types + if all_primitive_types[tp] == 'f') + +def all_signed_integer_types(ffi): + return [x for x in all_integer_types if int(ffi.cast(x, -1)) < 0] + +def all_unsigned_integer_types(ffi): + return [x for x in all_integer_types if int(ffi.cast(x, -1)) > 0] + + +def test_primitive_category(): + for typename in all_primitive_types: + tp = model.PrimitiveType(typename) + C = tp.is_char_type() + F = tp.is_float_type() + X = tp.is_complex_type() + I = tp.is_integer_type() + assert C == (typename in ('char', 'wchar_t', 'char16_t', 'char32_t')) + assert F == (typename in ('float', 'double', 'long double')) + assert X == (typename in ('float _Complex', 'double _Complex')) + assert I + F + C + X == 1 # one and only one of them is true + +def test_all_integer_and_float_types(): + typenames = [] + for typename in all_primitive_types: + if (all_primitive_types[typename] == 'c' or + all_primitive_types[typename] == 'j' or # complex + typename == '_Bool' or typename == 'long double'): + pass + else: + typenames.append(typename) + # + ffi = FFI() + ffi.cdef('\n'.join(["%s foo_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in typenames])) + lib = ffi.verify('\n'.join(["%s foo_%s(%s x) { return (%s)(x+1); }" % + (tp, tp.replace(' ', '_'), tp, tp) + for tp in typenames])) + for typename in typenames: + foo = getattr(lib, 'foo_%s' % typename.replace(' ', '_')) + assert foo(42) == 43 + if sys.version < '3': + assert foo(long(44)) == 45 + assert foo(ffi.cast(typename, 46)) == 47 + py.test.raises(TypeError, foo, ffi.NULL) + # + # check for overflow cases + if all_primitive_types[typename] == 'f': + continue + for value in [-2**80, -2**40, -2**20, -2**10, -2**5, -1, + 2**5, 2**10, 2**20, 2**40, 2**80]: + overflows = int(ffi.cast(typename, value)) != value + if overflows: + py.test.raises(OverflowError, foo, value) + else: + assert foo(value) == value + 1 + +def test_var_signed_integer_types(): + ffi = FFI() + lst = all_signed_integer_types(ffi) + csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + for tp in lst]) + ffi.cdef(csource) + lib = ffi.verify(csource) + for tp in lst: + varname = 'somevar_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + max = (1 << (8*sz-1)) - 1 + min = -(1 << (8*sz-1)) + setattr(lib, varname, max) + assert getattr(lib, varname) == max + setattr(lib, varname, min) + assert getattr(lib, varname) == min + py.test.raises(OverflowError, setattr, lib, varname, max+1) + py.test.raises(OverflowError, setattr, lib, varname, min-1) + +def test_var_unsigned_integer_types(): + ffi = FFI() + lst = all_unsigned_integer_types(ffi) + csource = "\n".join(["%s somevar_%s;" % (tp, tp.replace(' ', '_')) + for tp in lst]) + ffi.cdef(csource) + lib = ffi.verify(csource) + for tp in lst: + varname = 'somevar_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + if tp != '_Bool': + max = (1 << (8*sz)) - 1 + else: + max = 1 + setattr(lib, varname, max) + assert getattr(lib, varname) == max + setattr(lib, varname, 0) + assert getattr(lib, varname) == 0 + py.test.raises(OverflowError, setattr, lib, varname, max+1) + py.test.raises(OverflowError, setattr, lib, varname, -1) + +def test_fn_signed_integer_types(): + ffi = FFI() + lst = all_signed_integer_types(ffi) + cdefsrc = "\n".join(["%s somefn_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in lst]) + ffi.cdef(cdefsrc) + verifysrc = "\n".join(["%s somefn_%s(%s x) { return x; }" % + (tp, tp.replace(' ', '_'), tp) for tp in lst]) + lib = ffi.verify(verifysrc) + for tp in lst: + fnname = 'somefn_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + max = (1 << (8*sz-1)) - 1 + min = -(1 << (8*sz-1)) + fn = getattr(lib, fnname) + assert fn(max) == max + assert fn(min) == min + py.test.raises(OverflowError, fn, max + 1) + py.test.raises(OverflowError, fn, min - 1) + +def test_fn_unsigned_integer_types(): + ffi = FFI() + lst = all_unsigned_integer_types(ffi) + cdefsrc = "\n".join(["%s somefn_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in lst]) + ffi.cdef(cdefsrc) + verifysrc = "\n".join(["%s somefn_%s(%s x) { return x; }" % + (tp, tp.replace(' ', '_'), tp) for tp in lst]) + lib = ffi.verify(verifysrc) + for tp in lst: + fnname = 'somefn_%s' % tp.replace(' ', '_') + sz = ffi.sizeof(tp) + if tp != '_Bool': + max = (1 << (8*sz)) - 1 + else: + max = 1 + fn = getattr(lib, fnname) + assert fn(max) == max + assert fn(0) == 0 + py.test.raises(OverflowError, fn, max + 1) + py.test.raises(OverflowError, fn, -1) + +def test_char_type(): + ffi = FFI() + ffi.cdef("char foo(char);") + lib = ffi.verify("char foo(char x) { return ++x; }") + assert lib.foo(b"A") == b"B" + py.test.raises(TypeError, lib.foo, b"bar") + py.test.raises(TypeError, lib.foo, "bar") + +def test_wchar_type(): + ffi = FFI() + if ffi.sizeof('wchar_t') == 2: + uniexample1 = u+'\u1234' + uniexample2 = u+'\u1235' + else: + uniexample1 = u+'\U00012345' + uniexample2 = u+'\U00012346' + # + ffi.cdef("wchar_t foo(wchar_t);") + lib = ffi.verify("wchar_t foo(wchar_t x) { return x+1; }") + assert lib.foo(uniexample1) == uniexample2 + +def test_no_argument(): + ffi = FFI() + ffi.cdef("int foo(void);") + lib = ffi.verify("int foo(void) { return 42; }") + assert lib.foo() == 42 + +def test_two_arguments(): + ffi = FFI() + ffi.cdef("int foo(int, int);") + lib = ffi.verify("int foo(int a, int b) { return a - b; }") + assert lib.foo(40, -2) == 42 + +def test_macro(): + ffi = FFI() + ffi.cdef("int foo(int, int);") + lib = ffi.verify("#define foo(a, b) ((a) * (b))") + assert lib.foo(-6, -7) == 42 + +def test_ptr(): + ffi = FFI() + ffi.cdef("int *foo(int *);") + lib = ffi.verify("int *foo(int *a) { return a; }") + assert lib.foo(ffi.NULL) == ffi.NULL + p = ffi.new("int *", 42) + q = ffi.new("int *", 42) + assert lib.foo(p) == p + assert lib.foo(q) != p + +def test_bogus_ptr(): + ffi = FFI() + ffi.cdef("int *foo(int *);") + lib = ffi.verify("int *foo(int *a) { return a; }") + py.test.raises(TypeError, lib.foo, ffi.new("short *", 42)) + + +def test_verify_typedefs(): + py.test.skip("ignored so far") + types = ['signed char', 'unsigned char', 'int', 'long'] + for cdefed in types: + for real in types: + ffi = FFI() + ffi.cdef("typedef %s foo_t;" % cdefed) + if cdefed == real: + ffi.verify("typedef %s foo_t;" % real) + else: + py.test.raises(VerificationError, ffi.verify, + "typedef %s foo_t;" % real) + +def test_nondecl_struct(): + ffi = FFI() + ffi.cdef("typedef struct foo_s foo_t; int bar(foo_t *);") + lib = ffi.verify("typedef struct foo_s foo_t;\n" + "int bar(foo_t *f) { (void)f; return 42; }\n") + assert lib.bar(ffi.NULL) == 42 + +def test_ffi_full_struct(): + def check(verified_code): + ffi = FFI() + ffi.cdef("struct foo_s { char x; int y; long *z; };") + ffi.verify(verified_code) + ffi.new("struct foo_s *", {}) + + check("struct foo_s { char x; int y; long *z; };") + # + if sys.platform != 'win32': # XXX fixme: only gives warnings + py.test.raises(VerificationError, check, + "struct foo_s { char x; int y; int *z; };") + # + py.test.raises(VerificationError, check, + "struct foo_s { int y; long *z; };") # cdef'ed field x is missing + # + e = py.test.raises(FFI.error, check, + "struct foo_s { int y; char x; long *z; };") + assert str(e.value).startswith( + "struct foo_s: wrong offset for field 'x'" + " (cdef says 0, but C compiler says 4)") + # + e = py.test.raises(FFI.error, check, + "struct foo_s { char x; int y; long *z; char extra; };") + assert str(e.value).startswith( + "struct foo_s: wrong total size" + " (cdef says %d, but C compiler says %d)" % ( + 8 + FFI().sizeof('long *'), + 8 + FFI().sizeof('long *') * 2)) + # + # a corner case that we cannot really detect, but where it has no + # bad consequences: the size is the same, but there is an extra field + # that replaces what is just padding in our declaration above + check("struct foo_s { char x, extra; int y; long *z; };") + # + e = py.test.raises(FFI.error, check, + "struct foo_s { char x; short pad; short y; long *z; };") + assert str(e.value).startswith( + "struct foo_s: wrong size for field 'y'" + " (cdef says 4, but C compiler says 2)") + +def test_ffi_nonfull_struct(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { + int x; + ...; + }; + """) + py.test.raises(VerificationMissing, ffi.sizeof, 'struct foo_s') + py.test.raises(VerificationMissing, ffi.offsetof, 'struct foo_s', 'x') + py.test.raises(VerificationMissing, ffi.new, 'struct foo_s *') + ffi.verify(""" + struct foo_s { + int a, b, x, c, d, e; + }; + """) + assert ffi.sizeof('struct foo_s') == 6 * ffi.sizeof('int') + assert ffi.offsetof('struct foo_s', 'x') == 2 * ffi.sizeof('int') + +def test_ffi_nonfull_alignment(): + ffi = FFI() + ffi.cdef("struct foo_s { char x; ...; };") + ffi.verify("struct foo_s { int a, b; char x; };") + assert ffi.sizeof('struct foo_s') == 3 * ffi.sizeof('int') + assert ffi.alignof('struct foo_s') == ffi.sizeof('int') + +def _check_field_match(typename, real, expect_mismatch): + ffi = FFI() + testing_by_size = (expect_mismatch == 'by_size') + if testing_by_size: + expect_mismatch = ffi.sizeof(typename) != ffi.sizeof(real) + ffi.cdef("struct foo_s { %s x; ...; };" % typename) + try: + ffi.verify("struct foo_s { %s x; };" % real) + ffi.new("struct foo_s *", []) # because some mismatches show up lazily + except (VerificationError, ffi.error): + if not expect_mismatch: + if testing_by_size and typename != real: + print("ignoring mismatch between %s* and %s* even though " + "they have the same size" % (typename, real)) + return + raise AssertionError("unexpected mismatch: %s should be accepted " + "as equal to %s" % (typename, real)) + else: + if expect_mismatch: + raise AssertionError("mismatch not detected: " + "%s != %s" % (typename, real)) + +def test_struct_bad_sized_integer(): + for typename in ['int8_t', 'int16_t', 'int32_t', 'int64_t']: + for real in ['int8_t', 'int16_t', 'int32_t', 'int64_t']: + _check_field_match(typename, real, "by_size") + +def test_struct_bad_sized_float(): + for typename in all_float_types: + for real in all_float_types: + _check_field_match(typename, real, "by_size") + +def test_struct_signedness_ignored(): + _check_field_match("int", "unsigned int", expect_mismatch=False) + _check_field_match("unsigned short", "signed short", expect_mismatch=False) + +def test_struct_float_vs_int(): + if sys.platform == 'win32': + py.test.skip("XXX fixme: only gives warnings") + ffi = FFI() + for typename in all_signed_integer_types(ffi): + for real in all_float_types: + _check_field_match(typename, real, expect_mismatch=True) + for typename in all_float_types: + for real in all_signed_integer_types(ffi): + _check_field_match(typename, real, expect_mismatch=True) + +def test_struct_array_field(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[17]; ...; };") + ffi.verify("struct foo_s { int x; int a[17]; int y; };") + assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int') + +def test_struct_array_no_length(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[]; int y; ...; };\n" + "int bar(struct foo_s *);\n") + lib = ffi.verify("struct foo_s { int x; int a[17]; int y; };\n" + "int bar(struct foo_s *f) { return f->a[14]; }\n") + assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.typeof(s.a) is ffi.typeof('int[]') # implicit max length + assert len(s.a) == 18 # max length, computed from the size and start offset + s.a[14] = 4242 + assert lib.bar(s) == 4242 + # with no declared length, out-of-bound accesses are not detected + s.a[17] = -521 + assert s.y == s.a[17] == -521 + # + s = ffi.new("struct foo_s *", {'a': list(range(17))}) + assert s.a[16] == 16 + # overflows at construction time not detected either + s = ffi.new("struct foo_s *", {'a': list(range(18))}) + assert s.y == s.a[17] == 17 + +def test_struct_array_guess_length(): + ffi = FFI() + ffi.cdef("struct foo_s { int a[...]; };") + ffi.verify("struct foo_s { int x; int a[17]; int y; };") + assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int') + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int') + py.test.raises(IndexError, 's.a[17]') + +def test_struct_array_c99_1(): + if sys.platform == 'win32': + py.test.skip("requires C99") + ffi = FFI() + ffi.cdef("struct foo_s { int x; int a[]; };") + ffi.verify("struct foo_s { int x; int a[]; };") + assert ffi.sizeof('struct foo_s') == 1 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242, 4]) + assert ffi.sizeof(ffi.typeof(s[0])) == 1 * ffi.sizeof('int') + assert ffi.sizeof(s[0]) == 5 * ffi.sizeof('int') + # ^^^ explanation: if you write in C: "char x[5];", then + # "sizeof(x)" will evaluate to 5. The behavior above is + # a generalization of that to "struct foo_s[len(a)=5] x;" + # if you could do that in C. + assert s.a[3] == 0 + s = ffi.new("struct foo_s *", [424242, [-40, -30, -20, -10]]) + assert ffi.sizeof(s[0]) == 5 * ffi.sizeof('int') + assert s.a[3] == -10 + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242]) + assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int') + +def test_struct_array_c99_2(): + if sys.platform == 'win32': + py.test.skip("requires C99") + ffi = FFI() + ffi.cdef("struct foo_s { int x; int a[]; ...; };") + ffi.verify("struct foo_s { int x, y; int a[]; };") + assert ffi.sizeof('struct foo_s') == 2 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242, 4]) + assert ffi.sizeof(s[0]) == 6 * ffi.sizeof('int') + assert s.a[3] == 0 + s = ffi.new("struct foo_s *", [424242, [-40, -30, -20, -10]]) + assert ffi.sizeof(s[0]) == 6 * ffi.sizeof('int') + assert s.a[3] == -10 + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int') + s = ffi.new("struct foo_s *", [424242]) + assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int') + +def test_struct_ptr_to_array_field(): + ffi = FFI() + ffi.cdef("struct foo_s { int (*a)[17]; ...; }; struct bar_s { ...; };") + ffi.verify("struct foo_s { int x; int (*a)[17]; int y; };\n" + "struct bar_s { int x; int *a; int y; };") + assert ffi.sizeof('struct foo_s') == ffi.sizeof("struct bar_s") + s = ffi.new("struct foo_s *") + assert ffi.sizeof(s.a) == ffi.sizeof('int(*)[17]') == ffi.sizeof("int *") + +def test_struct_with_bitfield_exact(): + ffi = FFI() + ffi.cdef("struct foo_s { int a:2, b:3; };") + ffi.verify("struct foo_s { int a:2, b:3; };") + s = ffi.new("struct foo_s *") + s.b = 3 + py.test.raises(OverflowError, "s.b = 4") + assert s.b == 3 + +def test_struct_with_bitfield_enum(): + ffi = FFI() + code = """ + typedef enum { AA, BB, CC } foo_e; + typedef struct { foo_e f:2; } foo_s; + """ + ffi.cdef(code) + ffi.verify(code) + s = ffi.new("foo_s *") + s.f = 1 + assert s.f == 1 + if int(ffi.cast("foo_e", -1)) < 0: + two = -2 + else: + two = 2 + s.f = two + assert s.f == two + +def test_unsupported_struct_with_bitfield_ellipsis(): + ffi = FFI() + py.test.raises(NotImplementedError, ffi.cdef, + "struct foo_s { int a:2, b:3; ...; };") + +def test_global_constants(): + ffi = FFI() + # use 'static const int', as generally documented, although in this + # case the 'static' is completely ignored. + ffi.cdef("static const int AA, BB, CC, DD;") + lib = ffi.verify("#define AA 42\n" + "#define BB (-43) // blah\n" + "#define CC (22*2) /* foobar */\n" + "#define DD ((unsigned int)142) /* foo\nbar */\n") + assert lib.AA == 42 + assert lib.BB == -43 + assert lib.CC == 44 + assert lib.DD == 142 + +def test_global_const_int_size(): + # integer constants: ignore the declared type, always just use the value + for value in [-2**63, -2**31, -2**15, + 2**15-1, 2**15, 2**31-1, 2**31, 2**32-1, 2**32, + 2**63-1, 2**63, 2**64-1]: + ffi = FFI() + if value == int(ffi.cast("long long", value)): + if value < 0: + vstr = '(-%dLL-1)' % (~value,) + else: + vstr = '%dLL' % value + elif value == int(ffi.cast("unsigned long long", value)): + vstr = '%dULL' % value + else: + raise AssertionError(value) + ffi.cdef("static const unsigned short AA;") + lib = ffi.verify("#define AA %s\n" % vstr) + assert lib.AA == value + assert type(lib.AA) is type(int(lib.AA)) + +def test_global_constants_non_int(): + ffi = FFI() + ffi.cdef("static char *const PP;") + lib = ffi.verify('static char *const PP = "testing!";\n') + assert ffi.typeof(lib.PP) == ffi.typeof("char *") + assert ffi.string(lib.PP) == b"testing!" + +def test_nonfull_enum(): + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2, EE3, ... \n \t };") + py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE2') + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == "EE2" + assert ffi.string(ffi.cast('enum ee', -10)) == "EE3" + # + assert ffi.typeof("enum ee").relements == {'EE1': 10, 'EE2': 11, 'EE3': -10} + assert ffi.typeof("enum ee").elements == {10: 'EE1', 11: 'EE2', -10: 'EE3'} + +def test_full_enum(): + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2, EE3 };") + lib = ffi.verify("enum ee { EE1, EE2, EE3 };") + assert [lib.EE1, lib.EE2, lib.EE3] == [0, 1, 2] + +def test_enum_usage(): + ffi = FFI() + ffi.cdef("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;") + lib = ffi.verify("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;") + assert lib.EE2 == 1 + s = ffi.new("sp", [lib.EE2]) + assert s.x == 1 + s.x = 17 + assert s.x == 17 + +def test_anonymous_enum(): + ffi = FFI() + ffi.cdef("enum { EE1 }; enum { EE2, EE3 };") + lib = ffi.verify("enum { EE1 }; enum { EE2, EE3 };") + assert lib.EE1 == 0 + assert lib.EE2 == 0 + assert lib.EE3 == 1 + +def test_nonfull_anonymous_enum(): + ffi = FFI() + ffi.cdef("enum { EE1, ... }; enum { EE3, ... };") + lib = ffi.verify("enum { EE2, EE1 }; enum { EE3 };") + assert lib.EE1 == 1 + assert lib.EE3 == 0 + +def test_nonfull_enum_syntax2(): + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2=\t..., EE3 };") + py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1') + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2' + assert ffi.string(ffi.cast('enum ee', -10)) == 'EE3' + # + ffi = FFI() + ffi.cdef("enum ee { EE1, EE2=\t... };") + py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1') + ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };") + assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2' + # + ffi = FFI() + ffi.cdef("enum ee2 { EE4=..., EE5=..., ... };") + ffi.verify("enum ee2 { EE4=-1234-5, EE5 }; ") + assert ffi.string(ffi.cast('enum ee2', -1239)) == 'EE4' + assert ffi.string(ffi.cast('enum ee2', -1238)) == 'EE5' + +def test_get_set_errno(): + ffi = FFI() + ffi.cdef("int foo(int);") + lib = ffi.verify(""" + static int foo(int x) + { + errno += 1; + return x * 7; + } + """) + ffi.errno = 15 + assert lib.foo(6) == 42 + assert ffi.errno == 16 + +def test_define_int(): + ffi = FFI() + ffi.cdef("#define FOO ...\n" + "\t#\tdefine\tBAR\t...\t\n" + "#define BAZ ...\n") + lib = ffi.verify("#define FOO 42\n" + "#define BAR (-44)\n" + "#define BAZ 0xffffffffffffffffULL\n") + assert lib.FOO == 42 + assert lib.BAR == -44 + assert lib.BAZ == 0xffffffffffffffff + +def test_access_variable(): + ffi = FFI() + ffi.cdef("int foo(void);\n" + "int somenumber;") + lib = ffi.verify(""" + static int somenumber = 2; + static int foo(void) { + return somenumber * 7; + } + """) + assert lib.somenumber == 2 + assert lib.foo() == 14 + lib.somenumber = -6 + assert lib.foo() == -42 + assert lib.somenumber == -6 + lib.somenumber = 2 # reset for the next run, if any + +def test_access_address_of_variable(): + # access the address of 'somenumber': need a trick + ffi = FFI() + ffi.cdef("int somenumber; static int *const somenumberptr;") + lib = ffi.verify(""" + static int somenumber = 2; + #define somenumberptr (&somenumber) + """) + assert lib.somenumber == 2 + lib.somenumberptr[0] = 42 + assert lib.somenumber == 42 + lib.somenumber = 2 # reset for the next run, if any + +def test_access_array_variable(length=5): + ffi = FFI() + ffi.cdef("int foo(int);\n" + "int somenumber[%s];" % (length,)) + lib = ffi.verify(""" + static int somenumber[] = {2, 2, 3, 4, 5}; + static int foo(int i) { + return somenumber[i] * 7; + } + """) + if length == '': + # a global variable of an unknown array length is implicitly + # transformed into a global pointer variable, because we can only + # work with array instances whose length we know. using a pointer + # instead of an array gives the correct effects. + assert repr(lib.somenumber).startswith("x * 7; } + """) + f = ffi.new("foo_t *") + f.x = 6 + assert lib.foo(f) == 42 + +def test_unknown_type(): + ffi = FFI() + ffi.cdef(""" + typedef ... token_t; + int foo(token_t *); + #define TOKEN_SIZE ... + """) + lib = ffi.verify(""" + typedef float token_t; + static int foo(token_t *tk) { + if (!tk) + return -42; + *tk += 1.601f; + return (int)*tk; + } + #define TOKEN_SIZE sizeof(token_t) + """) + # we cannot let ffi.new("token_t *") work, because we don't know ahead of + # time if it's ok to ask 'sizeof(token_t)' in the C code or not. + # See test_unknown_type_2. Workaround. + tkmem = ffi.new("char[]", lib.TOKEN_SIZE) # zero-initialized + tk = ffi.cast("token_t *", tkmem) + results = [lib.foo(tk) for i in range(6)] + assert results == [1, 3, 4, 6, 8, 9] + assert lib.foo(ffi.NULL) == -42 + +def test_unknown_type_2(): + ffi = FFI() + ffi.cdef("typedef ... token_t;") + lib = ffi.verify("typedef struct token_s token_t;") + # assert did not crash, even though 'sizeof(token_t)' is not valid in C. + +def test_unknown_type_3(): + ffi = FFI() + ffi.cdef(""" + typedef ... *token_p; + token_p foo(token_p); + """) + lib = ffi.verify(""" + typedef struct _token_s *token_p; + token_p foo(token_p arg) { + if (arg) + return (token_p)0x12347; + else + return (token_p)0x12345; + } + """) + p = lib.foo(ffi.NULL) + assert int(ffi.cast("intptr_t", p)) == 0x12345 + q = lib.foo(p) + assert int(ffi.cast("intptr_t", q)) == 0x12347 + +def test_varargs(): + ffi = FFI() + ffi.cdef("int foo(int x, ...);") + lib = ffi.verify(""" + int foo(int x, ...) { + va_list vargs; + va_start(vargs, x); + x -= va_arg(vargs, int); + x -= va_arg(vargs, int); + va_end(vargs); + return x; + } + """) + assert lib.foo(50, ffi.cast("int", 5), ffi.cast("int", 3)) == 42 + +def test_varargs_exact(): + if sys.platform == 'win32': + py.test.skip("XXX fixme: only gives warnings") + ffi = FFI() + ffi.cdef("int foo(int x, ...);") + py.test.raises(VerificationError, ffi.verify, """ + int foo(long long x, ...) { + return x; + } + """) + +def test_varargs_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { char a; int b; }; int foo(int x, ...);") + lib = ffi.verify(""" + struct foo_s { + char a; int b; + }; + int foo(int x, ...) { + va_list vargs; + struct foo_s s; + va_start(vargs, x); + s = va_arg(vargs, struct foo_s); + va_end(vargs); + return s.a - s.b; + } + """) + s = ffi.new("struct foo_s *", [b'B', 1]) + assert lib.foo(50, s[0]) == ord('A') + +def test_autofilled_struct_as_argument(): + ffi = FFI() + ffi.cdef("struct foo_s { long a; double b; ...; };\n" + "int foo(struct foo_s);") + lib = ffi.verify(""" + struct foo_s { + double b; + long a; + }; + int foo(struct foo_s s) { + return (int)s.a - (int)s.b; + } + """) + s = ffi.new("struct foo_s *", [100, 1]) + assert lib.foo(s[0]) == 99 + assert lib.foo([100, 1]) == 99 + +def test_autofilled_struct_as_argument_dynamic(): + ffi = FFI() + ffi.cdef("struct foo_s { long a; ...; };\n" + "int (*foo)(struct foo_s);") + lib = ffi.verify(""" + struct foo_s { + double b; + long a; + }; + int foo1(struct foo_s s) { + return (int)s.a - (int)s.b; + } + 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; 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(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { int aa, bb; }; + struct foo_s foo(int a, int b); + """) + lib = ffi.verify(""" + struct foo_s { int aa, bb; }; + struct foo_s foo(int a, int b) { + struct foo_s r; + r.aa = a*a; + r.bb = b*b; + return r; + } + """) + s = lib.foo(6, 7) + assert repr(s) == "" + assert s.aa == 36 + assert s.bb == 49 + +def test_func_as_funcptr(): + ffi = FFI() + ffi.cdef("int *(*const fooptr)(void);") + lib = ffi.verify(""" + int *foo(void) { + return (int*)"foobar"; + } + int *(*fooptr)(void) = foo; + """) + foochar = ffi.cast("char *(*)(void)", lib.fooptr) + s = foochar() + assert ffi.string(s) == b"foobar" + +def test_funcptr_as_argument(): + ffi = FFI() + ffi.cdef(""" + void qsort(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *)); + """) + ffi.verify("#include ") + +def test_func_as_argument(): + ffi = FFI() + ffi.cdef(""" + void qsort(void *base, size_t nel, size_t width, + int compar(const void *, const void *)); + """) + ffi.verify("#include ") + +def test_array_as_argument(): + ffi = FFI() + ffi.cdef(""" + size_t strlen(char string[]); + """) + ffi.verify("#include ") + +def test_enum_as_argument(): + ffi = FFI() + ffi.cdef(""" + enum foo_e { AA, BB, ... }; + int foo_func(enum foo_e); + """) + lib = ffi.verify(""" + enum foo_e { AA, CC, BB }; + int foo_func(enum foo_e e) { return (int)e; } + """) + assert lib.foo_func(lib.BB) == 2 + py.test.raises(TypeError, lib.foo_func, "BB") + +def test_enum_as_function_result(): + ffi = FFI() + ffi.cdef(""" + enum foo_e { AA, BB, ... }; + enum foo_e foo_func(int x); + """) + lib = ffi.verify(""" + enum foo_e { AA, CC, BB }; + enum foo_e foo_func(int x) { return (enum foo_e)x; } + """) + assert lib.foo_func(lib.BB) == lib.BB == 2 + +def test_enum_values(): + ffi = FFI() + ffi.cdef("enum enum1_e { AA, BB };") + lib = ffi.verify("enum enum1_e { AA, BB };") + assert lib.AA == 0 + assert lib.BB == 1 + assert ffi.string(ffi.cast("enum enum1_e", 1)) == 'BB' + +def test_typedef_complete_enum(): + ffi = FFI() + ffi.cdef("typedef enum { AA, BB } enum1_t;") + lib = ffi.verify("typedef enum { AA, BB } enum1_t;") + assert ffi.string(ffi.cast("enum1_t", 1)) == 'BB' + assert lib.AA == 0 + assert lib.BB == 1 + +def test_typedef_broken_complete_enum(): + # xxx this is broken in old cffis, but works with recompiler.py + ffi = FFI() + ffi.cdef("typedef enum { AA, BB } enum1_t;") + lib = ffi.verify("typedef enum { AA, CC, BB } enum1_t;") + assert lib.AA == 0 + assert lib.BB == 2 + +def test_typedef_incomplete_enum(): + ffi = FFI() + ffi.cdef("typedef enum { AA, BB, ... } enum1_t;") + lib = ffi.verify("typedef enum { AA, CC, BB } enum1_t;") + assert ffi.string(ffi.cast("enum1_t", 1)) == '1' + assert ffi.string(ffi.cast("enum1_t", 2)) == 'BB' + assert lib.AA == 0 + assert lib.BB == 2 + +def test_typedef_enum_as_argument(): + ffi = FFI() + ffi.cdef(""" + typedef enum { AA, BB, ... } foo_t; + int foo_func(foo_t); + """) + lib = ffi.verify(""" + typedef enum { AA, CC, BB } foo_t; + int foo_func(foo_t e) { return (int)e; } + """) + assert lib.foo_func(lib.BB) == lib.BB == 2 + py.test.raises(TypeError, lib.foo_func, "BB") + +def test_typedef_enum_as_function_result(): + ffi = FFI() + ffi.cdef(""" + typedef enum { AA, BB, ... } foo_t; + foo_t foo_func(int x); + """) + lib = ffi.verify(""" + typedef enum { AA, CC, BB } foo_t; + foo_t foo_func(int x) { return (foo_t)x; } + """) + assert lib.foo_func(lib.BB) == lib.BB == 2 + +def test_function_typedef(): + ffi = FFI() + ffi.cdef(""" + typedef double func_t(double); + func_t sin; + """) + lib = ffi.verify('#include ', libraries=lib_m) + assert lib.sin(1.23) == math.sin(1.23) + +def test_opaque_integer_as_function_result(): + #import platform + #if platform.machine().startswith('sparc'): + # py.test.skip('Breaks horribly on sparc (SIGILL + corrupted stack)') + #elif platform.machine() == 'mips64' and sys.maxsize > 2**32: + # py.test.skip('Segfaults on mips64el') + # XXX bad abuse of "struct { ...; }". It only works a bit by chance + # anyway. XXX think about something better :-( + ffi = FFI() + ffi.cdef(""" + typedef struct { ...; } myhandle_t; + myhandle_t foo(void); + """) + lib = ffi.verify(""" + typedef short myhandle_t; + myhandle_t foo(void) { return 42; } + """) + h = lib.foo() + assert ffi.sizeof(h) == ffi.sizeof("short") + +def test_return_partial_struct(): + ffi = FFI() + ffi.cdef(""" + typedef struct { int x; ...; } foo_t; + foo_t foo(void); + """) + lib = ffi.verify(""" + typedef struct { int y, x; } foo_t; + foo_t foo(void) { foo_t r = { 45, 81 }; return r; } + """) + h = lib.foo() + assert ffi.sizeof(h) == 2 * ffi.sizeof("int") + assert h.x == 81 + +def test_take_and_return_partial_structs(): + ffi = FFI() + ffi.cdef(""" + typedef struct { int x; ...; } foo_t; + foo_t foo(foo_t, foo_t); + """) + lib = ffi.verify(""" + typedef struct { int y, x; } foo_t; + foo_t foo(foo_t a, foo_t b) { + foo_t r = { 100, a.x * 5 + b.x * 7 }; + return r; + } + """) + args = ffi.new("foo_t[3]") + args[0].x = 1000 + args[2].x = -498 + h = lib.foo(args[0], args[2]) + assert ffi.sizeof(h) == 2 * ffi.sizeof("int") + assert h.x == 1000 * 5 - 498 * 7 + +def test_cannot_name_struct_type(): + ffi = FFI() + ffi.cdef("typedef struct { int x; } **sp; void foo(sp);") + e = py.test.raises(VerificationError, ffi.verify, + "typedef struct { int x; } **sp; void foo(sp x) { }") + assert 'in argument of foo: unknown type name' in str(e.value) + +def test_dont_check_unnamable_fields(): + ffi = FFI() + ffi.cdef("struct foo_s { struct { int x; } someone; };") + ffi.verify("struct foo_s { struct { int x; } someone; };") + # assert did not crash + +def test_nested_anonymous_struct_exact(): + if sys.platform == 'win32': + py.test.skip("nested anonymous struct/union") + ffi = FFI() + ffi.cdef(""" + struct foo_s { struct { int a; char b; }; union { char c, d; }; }; + """) + assert ffi.offsetof("struct foo_s", "c") == 2 * ffi.sizeof("int") + assert ffi.sizeof("struct foo_s") == 3 * ffi.sizeof("int") + ffi.verify(""" + struct foo_s { struct { int a; char b; }; union { char c, d; }; }; + """) + p = ffi.new("struct foo_s *") + assert ffi.sizeof(p[0]) == 3 * ffi.sizeof("int") # with alignment + p.a = 1234567 + p.b = b'X' + p.c = b'Y' + assert p.a == 1234567 + assert p.b == b'X' + assert p.c == b'Y' + assert p.d == b'Y' + +def test_nested_anonymous_struct_exact_error(): + if sys.platform == 'win32': + py.test.skip("nested anonymous struct/union") + ffi = FFI() + ffi.cdef(""" + struct foo_s { struct { int a; char b; }; union { char c, d; }; }; + """) + py.test.raises(VerificationError, ffi.verify, """ + struct foo_s { struct { int a; short b; }; union { char c, d; }; }; + """) + # works fine now + #py.test.raises(VerificationError, ffi.verify, """ + # struct foo_s { struct { int a; char e, b; }; union { char c, d; }; }; + #""") + +def test_nested_anonymous_struct_inexact_1(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { struct { char b; ...; }; union { char c, d; }; }; + """) + ffi.verify(""" + struct foo_s { int a, padding; char c, d, b; }; + """) + assert ffi.sizeof("struct foo_s") == 3 * ffi.sizeof("int") + +def test_nested_anonymous_struct_inexact_2(): + ffi = FFI() + ffi.cdef(""" + struct foo_s { union { char c, d; }; struct { int a; char b; }; ...; }; + """) + ffi.verify(""" + struct foo_s { int a, padding; char c, d, b; }; + """) + assert ffi.sizeof("struct foo_s") == 3 * ffi.sizeof("int") + +def test_ffi_union(): + ffi = FFI() + ffi.cdef("union foo_u { char x; long *z; };") + ffi.verify("union foo_u { char x; int y; long *z; };") + +def test_ffi_union_partial(): + ffi = FFI() + ffi.cdef("union foo_u { char x; ...; };") + ffi.verify("union foo_u { char x; int y; };") + assert ffi.sizeof("union foo_u") == 4 + +def test_ffi_union_with_partial_struct(): + ffi = FFI() + ffi.cdef("struct foo_s { int x; ...; }; union foo_u { struct foo_s s; };") + ffi.verify("struct foo_s { int a; int x; }; " + "union foo_u { char b[32]; struct foo_s s; };") + assert ffi.sizeof("struct foo_s") == 8 + assert ffi.sizeof("union foo_u") == 32 + +def test_ffi_union_partial_2(): + ffi = FFI() + ffi.cdef("typedef union { char x; ...; } u1;") + ffi.verify("typedef union { char x; int y; } u1;") + assert ffi.sizeof("u1") == 4 + +def test_ffi_union_with_partial_struct_2(): + ffi = FFI() + ffi.cdef("typedef struct { int x; ...; } s1;" + "typedef union { s1 s; } u1;") + ffi.verify("typedef struct { int a; int x; } s1; " + "typedef union { char b[32]; s1 s; } u1;") + assert ffi.sizeof("s1") == 8 + assert ffi.sizeof("u1") == 32 + assert ffi.offsetof("u1", "s") == 0 + +def test_ffi_struct_packed(): + if sys.platform == 'win32': + py.test.skip("needs a GCC extension") + ffi = FFI() + ffi.cdef("struct foo_s { int b; ...; };") + ffi.verify(""" + struct foo_s { + char a; + int b; + } __attribute__((packed)); + """) + +def test_tmpdir(): + import tempfile, os + from testing.udir import udir + tmpdir = tempfile.mkdtemp(dir=str(udir)) + ffi = FFI() + ffi.cdef("int foo(int);") + lib = ffi.verify("int foo(int a) { return a + 42; }", tmpdir=tmpdir) + assert os.listdir(tmpdir) + assert lib.foo(100) == 142 + +def test_relative_to(): + py.test.skip("not available") + import tempfile, os + from testing.udir import udir + tmpdir = tempfile.mkdtemp(dir=str(udir)) + ffi = FFI() + ffi.cdef("int foo(int);") + f = open(os.path.join(tmpdir, 'foo.h'), 'w') + f.write("int foo(int a) { return a + 42; }\n") + f.close() + lib = ffi.verify('#include "foo.h"', + include_dirs=['.'], + relative_to=os.path.join(tmpdir, 'x')) + assert lib.foo(100) == 142 + +def test_bug1(): + ffi = FFI() + ffi.cdef(""" + typedef struct tdlhandle_s { ...; } *tdl_handle_t; + typedef struct my_error_code_ { + tdl_handle_t *rh; + } my_error_code_t; + """) + ffi.verify(""" + typedef struct tdlhandle_s { int foo; } *tdl_handle_t; + typedef struct my_error_code_ { + tdl_handle_t *rh; + } my_error_code_t; + """) + +def test_bool(): + if sys.platform == 'win32': + py.test.skip("_Bool not in MSVC") + ffi = FFI() + ffi.cdef("struct foo_s { _Bool x; };" + "_Bool foo(_Bool); _Bool (*foop)(_Bool);") + lib = ffi.verify(""" + struct foo_s { _Bool x; }; + int foo(int arg) { + return !arg; + } + _Bool _foofunc(_Bool x) { + return !x; + } + _Bool (*foop)(_Bool) = _foofunc; + """) + p = ffi.new("struct foo_s *") + p.x = 1 + assert p.x is True + py.test.raises(OverflowError, "p.x = -1") + py.test.raises(TypeError, "p.x = 0.0") + assert lib.foop(1) is False + assert lib.foop(True) is False + assert lib.foop(0) is True + py.test.raises(OverflowError, lib.foop, 42) + py.test.raises(TypeError, lib.foop, 0.0) + assert lib.foo(1) is False + assert lib.foo(True) is False + assert lib.foo(0) is True + py.test.raises(OverflowError, lib.foo, 42) + py.test.raises(TypeError, lib.foo, 0.0) + assert int(ffi.cast("_Bool", long(1))) == 1 + assert int(ffi.cast("_Bool", long(0))) == 0 + assert int(ffi.cast("_Bool", long(-1))) == 1 + assert int(ffi.cast("_Bool", 10**200)) == 1 + assert int(ffi.cast("_Bool", 10**40000)) == 1 + # + class Foo(object): + def __int__(self): + self.seen = 1 + return result + f = Foo() + f.seen = 0 + result = 42 + assert int(ffi.cast("_Bool", f)) == 1 + assert f.seen + f.seen = 0 + result = 0 + assert int(ffi.cast("_Bool", f)) == 0 + assert f.seen + # + py.test.raises(TypeError, ffi.cast, "_Bool", []) + +def test_bool_on_long_double(): + if sys.platform == 'win32': + py.test.skip("_Bool not in MSVC") + f = 1E-250 + if f == 0.0 or f*f != 0.0: + py.test.skip("unexpected precision") + ffi = FFI() + ffi.cdef("long double square(long double f); _Bool opposite(_Bool);") + lib = ffi.verify("long double square(long double f) { return f*f; }\n" + "_Bool opposite(_Bool x) { return !x; }") + f0 = lib.square(0.0) + f2 = lib.square(f) + f3 = lib.square(f * 2.0) + if repr(f2) == repr(f3): + py.test.skip("long double doesn't have enough precision") + assert float(f0) == float(f2) == float(f3) == 0.0 # too tiny for 'double' + assert int(ffi.cast("_Bool", f2)) == 1 + assert int(ffi.cast("_Bool", f3)) == 1 + assert int(ffi.cast("_Bool", f0)) == 0 + py.test.raises(TypeError, lib.opposite, f2) + +def test_cannot_pass_float(): + for basetype in ['char', 'short', 'int', 'long', 'long long']: + for sign in ['signed', 'unsigned']: + type = '%s %s' % (sign, basetype) + ffi = FFI() + ffi.cdef("struct foo_s { %s x; };\n" + "int foo(%s);" % (type, type)) + lib = ffi.verify(""" + struct foo_s { %s x; }; + int foo(%s arg) { + return !arg; + } + """ % (type, type)) + p = ffi.new("struct foo_s *") + py.test.raises(TypeError, "p.x = 0.0") + assert lib.foo(42) == 0 + assert lib.foo(0) == 1 + py.test.raises(TypeError, lib.foo, 0.0) + +def test_addressof(): + ffi = FFI() + ffi.cdef(""" + struct point_s { int x, y; }; + struct foo_s { int z; struct point_s point; }; + struct point_s sum_coord(struct point_s *); + """) + lib = ffi.verify(""" + struct point_s { int x, y; }; + struct foo_s { int z; struct point_s point; }; + struct point_s sum_coord(struct point_s *point) { + struct point_s r; + r.x = point->x + point->y; + r.y = point->x - point->y; + return r; + } + """) + p = ffi.new("struct foo_s *") + p.point.x = 16 + p.point.y = 9 + py.test.raises(TypeError, lib.sum_coord, p.point) + res = lib.sum_coord(ffi.addressof(p.point)) + assert res.x == 25 + assert res.y == 7 + res2 = lib.sum_coord(ffi.addressof(res)) + assert res2.x == 32 + assert res2.y == 18 + py.test.raises(TypeError, lib.sum_coord, res2) + +def test_callback_in_thread(): + py.test.xfail("adapt or remove") + if sys.platform == 'win32': + py.test.skip("pthread only") + import os, subprocess, imp + arg = os.path.join(os.path.dirname(__file__), 'callback_in_thread.py') + g = subprocess.Popen([sys.executable, arg, + os.path.dirname(imp.find_module('cffi')[1])]) + result = g.wait() + assert result == 0 + +def test_keepalive_lib(): + py.test.xfail("adapt or remove") + ffi = FFI() + ffi.cdef("int foobar(void);") + lib = ffi.verify("int foobar(void) { return 42; }") + func = lib.foobar + ffi_r = weakref.ref(ffi) + lib_r = weakref.ref(lib) + del ffi + import gc; gc.collect() # lib stays alive + assert lib_r() is not None + assert ffi_r() is not None + assert func() == 42 + +def test_keepalive_ffi(): + py.test.xfail("adapt or remove") + ffi = FFI() + ffi.cdef("int foobar(void);") + lib = ffi.verify("int foobar(void) { return 42; }") + func = lib.foobar + ffi_r = weakref.ref(ffi) + lib_r = weakref.ref(lib) + del lib + import gc; gc.collect() # ffi stays alive + assert ffi_r() is not None + assert lib_r() is not None + assert func() == 42 + +def test_FILE_stored_in_stdout(): + if not sys.platform.startswith('linux'): + py.test.skip("likely, we cannot assign to stdout") + ffi = FFI() + ffi.cdef("int printf(const char *, ...); FILE *setstdout(FILE *);") + lib = ffi.verify(""" + #include + FILE *setstdout(FILE *f) { + FILE *result = stdout; + stdout = f; + return result; + } + """) + import os + fdr, fdw = os.pipe() + fw1 = os.fdopen(fdw, 'wb', 256) + old_stdout = lib.setstdout(fw1) + try: + # + fw1.write(b"X") + r = lib.printf(b"hello, %d!\n", ffi.cast("int", 42)) + fw1.close() + assert r == len("hello, 42!\n") + # + finally: + lib.setstdout(old_stdout) + # + result = os.read(fdr, 256) + os.close(fdr) + # the 'X' might remain in the user-level buffer of 'fw1' and + # end up showing up after the 'hello, 42!\n' + assert result == b"Xhello, 42!\n" or result == b"hello, 42!\nX" + +def test_FILE_stored_explicitly(): + ffi = FFI() + ffi.cdef("int myprintf11(const char *, int); FILE *myfile;") + lib = ffi.verify(""" + #include + FILE *myfile; + int myprintf11(const char *out, int value) { + return fprintf(myfile, out, value); + } + """) + import os + fdr, fdw = os.pipe() + fw1 = os.fdopen(fdw, 'wb', 256) + lib.myfile = ffi.cast("FILE *", fw1) + # + fw1.write(b"X") + r = lib.myprintf11(b"hello, %d!\n", ffi.cast("int", 42)) + fw1.close() + assert r == len("hello, 42!\n") + # + result = os.read(fdr, 256) + os.close(fdr) + # the 'X' might remain in the user-level buffer of 'fw1' and + # end up showing up after the 'hello, 42!\n' + assert result == b"Xhello, 42!\n" or result == b"hello, 42!\nX" + +def test_global_array_with_missing_length(): + ffi = FFI() + ffi.cdef("int fooarray[];") + lib = ffi.verify("int fooarray[50];") + assert repr(lib.fooarray).startswith("x; }") + res = lib.myfunc(ffi2.new("foo_t *", {'x': 10})) + assert res == 420 + res = lib.myfunc(ffi1.new("foo_t *", {'x': -10})) + assert res == -420 + +def test_include_enum(): + ffi1 = FFI() + ffi1.cdef("enum foo_e { AA, ... };") + lib1 = ffi1.verify("enum foo_e { CC, BB, AA };") + ffi2 = FFI() + ffi2.include(ffi1) + ffi2.cdef("int myfunc(enum foo_e);") + lib2 = ffi2.verify("enum foo_e { CC, BB, AA };" + "int myfunc(enum foo_e x) { return (int)x; }") + res = lib2.myfunc(lib2.AA) + assert res == 2 + +def test_named_pointer_as_argument(): + ffi = FFI() + ffi.cdef("typedef struct { int x; } *mystruct_p;\n" + "mystruct_p ff5a(mystruct_p);") + lib = ffi.verify("typedef struct { int x; } *mystruct_p;\n" + "mystruct_p ff5a(mystruct_p p) { p->x += 40; return p; }") + p = ffi.new("mystruct_p", [-2]) + q = lib.ff5a(p) + assert q == p + assert p.x == 38 + +def test_enum_size(): + cases = [('123', 4, 4294967295), + ('4294967295U', 4, 4294967295), + ('-123', 4, -1), + ('-2147483647-1', 4, -1), + ] + if FFI().sizeof("long") == 8: + cases += [('4294967296L', 8, 2**64-1), + ('%dUL' % (2**64-1), 8, 2**64-1), + ('-2147483649L', 8, -1), + ('%dL-1L' % (1-2**63), 8, -1)] + for hidden_value, expected_size, expected_minus1 in cases: + if sys.platform == 'win32' and 'U' in hidden_value: + continue # skipped on Windows + ffi = FFI() + ffi.cdef("enum foo_e { AA, BB, ... };") + lib = ffi.verify("enum foo_e { AA, BB=%s };" % hidden_value) + assert lib.AA == 0 + assert lib.BB == eval(hidden_value.replace('U', '').replace('L', '')) + assert ffi.sizeof("enum foo_e") == expected_size + if sys.platform != 'win32': + assert int(ffi.cast("enum foo_e", -1)) == expected_minus1 + # test with the large value hidden: + # disabled so far, doesn't work +## for hidden_value, expected_size, expected_minus1 in cases: +## ffi = FFI() +## ffi.cdef("enum foo_e { AA, BB, ... };") +## lib = ffi.verify("enum foo_e { AA, BB=%s };" % hidden_value) +## assert lib.AA == 0 +## assert ffi.sizeof("enum foo_e") == expected_size +## assert int(ffi.cast("enum foo_e", -1)) == expected_minus1 + +def test_enum_bug118(): + maxulong = 256 ** FFI().sizeof("unsigned long") - 1 + for c2, c2c in [(-1, ''), + (-1, ''), + (0xffffffff, 'U'), + (maxulong, 'UL'), + (-int(maxulong / 3), 'L')]: + if c2c and sys.platform == 'win32': + continue # enums may always be signed with MSVC + ffi = FFI() + ffi.cdef("enum foo_e { AA };") + lib = ffi.verify("enum foo_e { AA=%s%s };" % (c2, c2c)) + assert lib.AA == c2 + +def test_string_to_voidp_arg(): + ffi = FFI() + ffi.cdef("int myfunc(void *);") + lib = ffi.verify("int myfunc(void *p) { return ((signed char *)p)[0]; }") + res = lib.myfunc(b"hi!") + assert res == ord(b"h") + p = ffi.new("char[]", b"gah") + res = lib.myfunc(p) + assert res == ord(b"g") + res = lib.myfunc(ffi.cast("void *", p)) + assert res == ord(b"g") + res = lib.myfunc(ffi.cast("int *", p)) + assert res == ord(b"g") + +def test_callback_indirection(): + ffi = FFI() + ffi.cdef(""" + int (*python_callback)(int how_many, int *values); + int (*const c_callback)(int,...); /* pass this ptr to C routines */ + int some_c_function(int(*cb)(int,...)); + """) + lib = ffi.verify(""" + #include + #ifdef _WIN32 + #include + #define alloca _alloca + #else + # ifdef __FreeBSD__ + # include + # else + # include + # endif + #endif + static int (*python_callback)(int how_many, int *values); + static int c_callback(int how_many, ...) { + va_list ap; + /* collect the "..." arguments into the values[] array */ + int i, *values = alloca((size_t)how_many * sizeof(int)); + va_start(ap, how_many); + for (i=0; i" + +def test_bug_const_char_ptr_array_1(): + ffi = FFI() + ffi.cdef("""const char *a[...];""") + lib = ffi.verify("""const char *a[5];""") + assert repr(ffi.typeof(lib.a)) == "" + +def test_bug_const_char_ptr_array_2(): + ffi = FFI() + ffi.cdef("""const int a[];""") + lib = ffi.verify("""const int a[5];""") + assert repr(ffi.typeof(lib.a)) == "" + +def _test_various_calls(force_libffi): + cdef_source = """ + int xvalue; + long long ivalue, rvalue; + float fvalue; + double dvalue; + long double Dvalue; + signed char tf_bb(signed char x, signed char c); + unsigned char tf_bB(signed char x, unsigned char c); + short tf_bh(signed char x, short c); + unsigned short tf_bH(signed char x, unsigned short c); + int tf_bi(signed char x, int c); + unsigned int tf_bI(signed char x, unsigned int c); + long tf_bl(signed char x, long c); + unsigned long tf_bL(signed char x, unsigned long c); + long long tf_bq(signed char x, long long c); + unsigned long long tf_bQ(signed char x, unsigned long long c); + float tf_bf(signed char x, float c); + double tf_bd(signed char x, double c); + long double tf_bD(signed char x, long double c); + """ + if force_libffi: + cdef_source = (cdef_source + .replace('tf_', '(*const tf_') + .replace('(signed char x', ')(signed char x')) + ffi = FFI() + ffi.cdef(cdef_source) + lib = ffi.verify(""" + int xvalue; + long long ivalue, rvalue; + float fvalue; + double dvalue; + long double Dvalue; + + typedef signed char b_t; + typedef unsigned char B_t; + typedef short h_t; + typedef unsigned short H_t; + typedef int i_t; + typedef unsigned int I_t; + typedef long l_t; + typedef unsigned long L_t; + typedef long long q_t; + typedef unsigned long long Q_t; + typedef float f_t; + typedef double d_t; + typedef long double D_t; + #define S(letter) xvalue = (int)x; letter##value = (letter##_t)c; + #define R(letter) return (letter##_t)rvalue; + + signed char tf_bb(signed char x, signed char c) { S(i) R(b) } + unsigned char tf_bB(signed char x, unsigned char c) { S(i) R(B) } + short tf_bh(signed char x, short c) { S(i) R(h) } + unsigned short tf_bH(signed char x, unsigned short c) { S(i) R(H) } + int tf_bi(signed char x, int c) { S(i) R(i) } + unsigned int tf_bI(signed char x, unsigned int c) { S(i) R(I) } + long tf_bl(signed char x, long c) { S(i) R(l) } + unsigned long tf_bL(signed char x, unsigned long c) { S(i) R(L) } + long long tf_bq(signed char x, long long c) { S(i) R(q) } + unsigned long long tf_bQ(signed char x, unsigned long long c) { S(i) R(Q) } + float tf_bf(signed char x, float c) { S(f) R(f) } + double tf_bd(signed char x, double c) { S(d) R(d) } + long double tf_bD(signed char x, long double c) { S(D) R(D) } + """) + lib.rvalue = 0x7182838485868788 + for kind, cname in [('b', 'signed char'), + ('B', 'unsigned char'), + ('h', 'short'), + ('H', 'unsigned short'), + ('i', 'int'), + ('I', 'unsigned int'), + ('l', 'long'), + ('L', 'unsigned long'), + ('q', 'long long'), + ('Q', 'unsigned long long'), + ('f', 'float'), + ('d', 'double'), + ('D', 'long double')]: + sign = +1 if 'unsigned' in cname else -1 + lib.xvalue = 0 + lib.ivalue = 0 + lib.fvalue = 0 + lib.dvalue = 0 + lib.Dvalue = 0 + fun = getattr(lib, 'tf_b' + kind) + res = fun(-42, sign * 99) + if kind == 'D': + res = float(res) + assert res == int(ffi.cast(cname, 0x7182838485868788)) + assert lib.xvalue == -42 + if kind in 'fdD': + assert float(getattr(lib, kind + 'value')) == -99.0 + else: + assert lib.ivalue == sign * 99 + +def test_various_calls_direct(): + _test_various_calls(force_libffi=False) + +def test_various_calls_libffi(): + _test_various_calls(force_libffi=True) + +def test_ptr_to_opaque(): + ffi = FFI() + ffi.cdef("typedef ... foo_t; int f1(foo_t*); foo_t *f2(int);") + lib = ffi.verify(""" + #include + typedef struct { int x; } foo_t; + int f1(foo_t* p) { + int x = p->x; + free(p); + return x; + } + foo_t *f2(int x) { + foo_t *p = malloc(sizeof(foo_t)); + p->x = x; + return p; + } + """) + p = lib.f2(42) + x = lib.f1(p) + assert x == 42 + +def _run_in_multiple_threads(test1): + test1() + import sys + try: + import thread + except ImportError: + import _thread as thread + errors = [] + def wrapper(lock): + try: + test1() + except: + errors.append(sys.exc_info()) + lock.release() + locks = [] + for i in range(10): + _lock = thread.allocate_lock() + _lock.acquire() + thread.start_new_thread(wrapper, (_lock,)) + locks.append(_lock) + for _lock in locks: + _lock.acquire() + if errors: + raise errors[0][1] + +def test_errno_working_even_with_pypys_jit(): + ffi = FFI() + ffi.cdef("int f(int);") + lib = ffi.verify(""" + #include + int f(int x) { return (errno = errno + x); } + """) + @_run_in_multiple_threads + def test1(): + ffi.errno = 0 + for i in range(10000): + e = lib.f(1) + assert e == i + 1 + assert ffi.errno == e + for i in range(10000): + ffi.errno = i + e = lib.f(42) + assert e == i + 42 + +def test_getlasterror_working_even_with_pypys_jit(): + if sys.platform != 'win32': + py.test.skip("win32-only test") + ffi = FFI() + ffi.cdef("void SetLastError(DWORD);") + lib = ffi.dlopen("Kernel32.dll") + @_run_in_multiple_threads + def test1(): + for i in range(10000): + n = (1 << 29) + i + lib.SetLastError(n) + assert ffi.getwinerror()[0] == n + +def test_verify_dlopen_flags(): + if not hasattr(sys, 'setdlopenflags'): + py.test.skip("requires sys.setdlopenflags()") + # Careful with RTLD_GLOBAL. If by chance the FFI is not deleted + # promptly, like on PyPy, then other tests may see the same + # exported symbols as well. So we must not export a simple name + # like 'foo'! + old = sys.getdlopenflags() + try: + ffi1 = FFI() + ffi1.cdef("int foo_verify_dlopen_flags_1;") + sys.setdlopenflags(ffi1.RTLD_GLOBAL | ffi1.RTLD_NOW) + lib1 = ffi1.verify("int foo_verify_dlopen_flags_1;") + finally: + sys.setdlopenflags(old) + + ffi2 = FFI() + ffi2.cdef("int *getptr(void);") + lib2 = ffi2.verify(""" + extern int foo_verify_dlopen_flags_1; + static int *getptr(void) { return &foo_verify_dlopen_flags_1; } + """) + p = lib2.getptr() + assert ffi1.addressof(lib1, 'foo_verify_dlopen_flags_1') == p + +def test_consider_not_implemented_function_type(): + ffi = FFI() + ffi.cdef("typedef union { int a; float b; } Data;" + "typedef struct { int a:2; } MyStr;" + "typedef void (*foofunc_t)(Data);" + "typedef Data (*bazfunc_t)(void);" + "typedef MyStr (*barfunc_t)(void);") + fooptr = ffi.cast("foofunc_t", 123) + bazptr = ffi.cast("bazfunc_t", 123) + barptr = ffi.cast("barfunc_t", 123) + # assert did not crash so far + e = py.test.raises(NotImplementedError, fooptr, ffi.new("Data *")) + assert str(e.value) == ( + "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' 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, 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() + ffi.cdef("#define ABA ...") + lib = ffi.verify("", define_macros=[('ABA', '42')]) + assert lib.ABA == 42 + +def test_implicit_unicode_on_windows(): + from cffi import FFIError + if sys.platform != 'win32': + py.test.skip("win32-only test") + ffi = FFI() + e = py.test.raises(FFIError, ffi.cdef, "int foo(LPTSTR);") + assert str(e.value) == ("The Windows type 'LPTSTR' is only available after" + " you call ffi.set_unicode()") + for with_unicode in [True, False]: + ffi = FFI() + ffi.set_unicode(with_unicode) + ffi.cdef(""" + DWORD GetModuleFileName(HMODULE hModule, LPTSTR lpFilename, + DWORD nSize); + """) + lib = ffi.verify(""" + #include + """, libraries=['Kernel32']) + outbuf = ffi.new("TCHAR[]", 200) + n = lib.GetModuleFileName(ffi.NULL, outbuf, 500) + assert 0 < n < 500 + for i in range(n): + #print repr(outbuf[i]) + assert ord(outbuf[i]) != 0 + assert ord(outbuf[n]) == 0 + assert ord(outbuf[0]) < 128 # should be a letter, or '\' + +def test_define_known_value(): + ffi = FFI() + ffi.cdef("#define FOO 0x123") + lib = ffi.verify("#define FOO 0x123") + assert lib.FOO == 0x123 + +def test_define_wrong_value(): + ffi = FFI() + ffi.cdef("#define FOO 123") + lib = ffi.verify("#define FOO 124") # used to complain + e = py.test.raises(ffi.error, "lib.FOO") + assert str(e.value) == ("the C compiler says 'FOO' is equal to 124 (0x7c)," + " but the cdef disagrees") + +def test_some_integer_type_for_issue73(): + ffi = FFI() + ffi.cdef(""" + typedef int... AnIntegerWith32Bits; + typedef AnIntegerWith32Bits (*AFunctionReturningInteger) (void); + AnIntegerWith32Bits InvokeFunction(AFunctionReturningInteger); + """) + lib = ffi.verify(""" + #ifdef __LP64__ + typedef int AnIntegerWith32Bits; + #else + typedef long AnIntegerWith32Bits; + #endif + typedef AnIntegerWith32Bits (*AFunctionReturningInteger) (void); + AnIntegerWith32Bits InvokeFunction(AFunctionReturningInteger f) { + return f(); + } + """) + @ffi.callback("AFunctionReturningInteger") + def add(): + return 3 + 4 + x = lib.InvokeFunction(add) + assert x == 7 + +def test_unsupported_some_primitive_types(): + ffi = FFI() + py.test.raises((FFIError, # with pycparser <= 2.17 + CDefError), # with pycparser >= 2.18 + ffi.cdef, """typedef void... foo_t;""") + # + ffi.cdef("typedef int... foo_t;") + py.test.raises(VerificationError, ffi.verify, "typedef float foo_t;") + +def test_windows_dllimport_data(): + if sys.platform != 'win32': + py.test.skip("Windows only") + from testing.udir import udir + tmpfile = udir.join('dllimport_data.c') + tmpfile.write('int my_value = 42;\n') + ffi = FFI() + ffi.cdef("int my_value;") + lib = ffi.verify("extern __declspec(dllimport) int my_value;", + sources = [str(tmpfile)]) + assert lib.my_value == 42 + +def test_macro_var(): + ffi = FFI() + ffi.cdef("int myarray[50], my_value;") + lib = ffi.verify(""" + int myarray[50]; + int *get_my_value(void) { + static int index = 0; + return &myarray[index++]; + } + #define my_value (*get_my_value()) + """) + assert lib.my_value == 0 # [0] + lib.my_value = 42 # [1] + assert lib.myarray[1] == 42 + assert lib.my_value == 0 # [2] + lib.myarray[3] = 63 + assert lib.my_value == 63 # [3] + p = ffi.addressof(lib, 'my_value') # [4] + assert p[-1] == 63 + assert p[0] == 0 + assert p == lib.myarray + 4 + p[1] = 82 + assert lib.my_value == 82 # [5] + +def test_const_pointer_to_pointer(): + ffi = FFI() + ffi.cdef("struct s { char *const *a; };") + ffi.verify("struct s { char *const *a; };") + +def test_share_FILE(): + ffi1 = FFI() + ffi1.cdef("void do_stuff(FILE *);") + lib1 = ffi1.verify("void do_stuff(FILE *f) { (void)f; }") + ffi2 = FFI() + ffi2.cdef("FILE *barize(void);") + lib2 = ffi2.verify("FILE *barize(void) { return NULL; }") + lib1.do_stuff(lib2.barize()) + +def test_win_common_types(): + if sys.platform != 'win32': + py.test.skip("Windows only") + ffi = FFI() + ffi.set_unicode(True) + ffi.verify("") + assert ffi.typeof("PBYTE") is ffi.typeof("unsigned char *") + if sys.maxsize > 2**32: + expected = "unsigned long long" + else: + expected = "unsigned int" + assert ffi.typeof("UINT_PTR") is ffi.typeof(expected) + assert ffi.typeof("PTSTR") is ffi.typeof("wchar_t *") + +def _only_test_on_linux_intel(): + if not sys.platform.startswith('linux'): + py.test.skip('only running the memory-intensive test on Linux') + import platform + machine = platform.machine() + if 'x86' not in machine and 'x64' not in machine: + py.test.skip('only running the memory-intensive test on x86/x64') + +def test_ffi_gc_size_arg(): + _only_test_on_linux_intel() + ffi = FFI() + ffi.cdef("void *malloc(size_t); void free(void *);") + lib = ffi.verify(r""" + #include + """) + for i in range(2000): + p = lib.malloc(20*1024*1024) # 20 MB + p1 = ffi.cast("char *", p) + for j in range(0, 20*1024*1024, 4096): + p1[j] = b'!' + p = ffi.gc(p, lib.free, 20*1024*1024) + del p + # with PyPy's GC, the above would rapidly consume 40 GB of RAM + # without the third argument to ffi.gc() + +def test_ffi_gc_size_arg_2(): + # a variant of the above: this "attack" works on cpython's cyclic gc too + # and I found no obvious way to prevent that. So for now, this test + # is skipped on CPython, where it eats all the memory. + if '__pypy__' not in sys.builtin_module_names: + py.test.skip("find a way to tweak the cyclic GC of CPython") + _only_test_on_linux_intel() + ffi = FFI() + ffi.cdef("void *malloc(size_t); void free(void *);") + lib = ffi.verify(r""" + #include + """) + class X(object): + pass + for i in range(2000): + p = lib.malloc(50*1024*1024) # 50 MB + p1 = ffi.cast("char *", p) + for j in range(0, 50*1024*1024, 4096): + p1[j] = b'!' + p = ffi.gc(p, lib.free, 50*1024*1024) + x = X() + x.p = p + x.cyclic = x + del p, x + +def test_ffi_new_with_cycles(): + # still another variant, with ffi.new() + if '__pypy__' not in sys.builtin_module_names: + py.test.skip("find a way to tweak the cyclic GC of CPython") + ffi = FFI() + ffi.cdef("") + lib = ffi.verify("") + class X(object): + pass + for i in range(2000): + p = ffi.new("char[]", 50*1024*1024) # 50 MB + for j in range(0, 50*1024*1024, 4096): + p[j] = b'!' + x = X() + x.p = p + x.cyclic = x + del p, x diff --git a/testing/cffi1/test_zdist.py b/testing/cffi1/test_zdist.py new file mode 100644 index 0000000..efc1d86 --- /dev/null +++ b/testing/cffi1/test_zdist.py @@ -0,0 +1,426 @@ +import sys, os, py +import subprocess +import cffi +from testing.udir import udir +from shutil import rmtree +from tempfile import mkdtemp + + +def chdir_to_tmp(f): + f.chdir_to_tmp = True + return f + +def from_outside(f): + f.chdir_to_tmp = False + return f + + +class TestDist(object): + + def setup_method(self, meth): + self.executable = os.path.abspath(sys.executable) + self.rootdir = os.path.abspath(os.path.dirname(os.path.dirname( + cffi.__file__))) + self.udir = udir.join(meth.__name__) + os.mkdir(str(self.udir)) + if meth.chdir_to_tmp: + self.saved_cwd = os.getcwd() + os.chdir(str(self.udir)) + + def teardown_method(self, meth): + if hasattr(self, 'saved_cwd'): + os.chdir(self.saved_cwd) + + def run(self, args, cwd=None): + env = os.environ.copy() + # a horrible hack to prevent distutils from finding ~/.pydistutils.cfg + # (there is the --no-user-cfg option, but not in Python 2.6...) + # NOTE: pointing $HOME to a nonexistent directory can break certain things + # that look there for configuration (like ccache). + tmp_home = mkdtemp() + assert tmp_home != None, "cannot create temporary homedir" + env['HOME'] = tmp_home + if cwd is None: + newpath = self.rootdir + if 'PYTHONPATH' in env: + newpath += os.pathsep + env['PYTHONPATH'] + env['PYTHONPATH'] = newpath + try: + subprocess.check_call([self.executable] + args, cwd=cwd, env=env) + finally: + rmtree(tmp_home) + + def _prepare_setuptools(self): + if hasattr(TestDist, '_setuptools_ready'): + return + try: + import setuptools + except ImportError: + py.test.skip("setuptools not found") + if os.path.exists(os.path.join(self.rootdir, 'setup.py')): + self.run(['setup.py', 'egg_info'], cwd=self.rootdir) + TestDist._setuptools_ready = True + + def check_produced_files(self, content, curdir=None): + if curdir is None: + curdir = str(self.udir) + found_so = None + for name in os.listdir(curdir): + if (name.endswith('.so') or name.endswith('.pyd') or + name.endswith('.dylib') or name.endswith('.dll')): + found_so = os.path.join(curdir, name) + # foo.so => foo + parts = name.split('.') + del parts[-1] + if len(parts) > 1 and parts[-1] != 'bar': + # foo.cpython-34m.so => foo, but foo.bar.so => foo.bar + del parts[-1] + name = '.'.join(parts) + # foo_d => foo (Python 2 debug builds) + if name.endswith('_d') and hasattr(sys, 'gettotalrefcount'): + name = name[:-2] + name += '.SO' + if name.startswith('pycparser') and name.endswith('.egg'): + continue # no clue why this shows up sometimes and not others + if name == '.eggs': + continue # seems new in 3.5, ignore it + assert name in content, "found unexpected file %r" % ( + os.path.join(curdir, name),) + value = content.pop(name) + if value is None: + assert name.endswith('.SO') or ( + os.path.isfile(os.path.join(curdir, name))) + else: + subdir = os.path.join(curdir, name) + assert os.path.isdir(subdir) + if value == '?': + continue + found_so = self.check_produced_files(value, subdir) or found_so + assert content == {}, "files or dirs not produced in %r: %r" % ( + curdir, content.keys()) + return found_so + + @chdir_to_tmp + def test_empty(self): + self.check_produced_files({}) + + @chdir_to_tmp + def test_abi_emit_python_code_1(self): + ffi = cffi.FFI() + ffi.set_source("package_name_1.mymod", None) + ffi.emit_python_code('xyz.py') + self.check_produced_files({'xyz.py': None}) + + @chdir_to_tmp + def test_abi_emit_python_code_2(self): + ffi = cffi.FFI() + ffi.set_source("package_name_1.mymod", None) + py.test.raises(IOError, ffi.emit_python_code, 'unexisting/xyz.py') + + @from_outside + def test_abi_emit_python_code_3(self): + ffi = cffi.FFI() + ffi.set_source("package_name_1.mymod", None) + ffi.emit_python_code(str(self.udir.join('xyt.py'))) + self.check_produced_files({'xyt.py': None}) + + @chdir_to_tmp + def test_abi_compile_1(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", None) + x = ffi.compile() + self.check_produced_files({'mod_name_in_package': {'mymod.py': None}}) + assert x == os.path.join('.', 'mod_name_in_package', 'mymod.py') + + @chdir_to_tmp + def test_abi_compile_2(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", None) + x = ffi.compile('build2') + self.check_produced_files({'build2': { + 'mod_name_in_package': {'mymod.py': None}}}) + assert x == os.path.join('build2', 'mod_name_in_package', 'mymod.py') + + @from_outside + def test_abi_compile_3(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", None) + tmpdir = str(self.udir.join('build3')) + x = ffi.compile(tmpdir) + self.check_produced_files({'build3': { + 'mod_name_in_package': {'mymod.py': None}}}) + assert x == os.path.join(tmpdir, 'mod_name_in_package', 'mymod.py') + + @chdir_to_tmp + def test_api_emit_c_code_1(self): + ffi = cffi.FFI() + ffi.set_source("package_name_1.mymod", "/*code would be here*/") + ffi.emit_c_code('xyz.c') + self.check_produced_files({'xyz.c': None}) + + @chdir_to_tmp + def test_api_emit_c_code_2(self): + ffi = cffi.FFI() + ffi.set_source("package_name_1.mymod", "/*code would be here*/") + py.test.raises(IOError, ffi.emit_c_code, 'unexisting/xyz.c') + + @from_outside + def test_api_emit_c_code_3(self): + ffi = cffi.FFI() + ffi.set_source("package_name_1.mymod", "/*code would be here*/") + ffi.emit_c_code(str(self.udir.join('xyu.c'))) + self.check_produced_files({'xyu.c': None}) + + @chdir_to_tmp + def test_api_compile_1(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile() + if sys.platform != 'win32': + sofile = self.check_produced_files({ + 'mod_name_in_package': {'mymod.SO': None, + 'mymod.c': None, + 'mymod.o': None}}) + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'mod_name_in_package': {'mymod.SO': None, + 'mymod.c': None}, + 'Release': '?'}) + + @chdir_to_tmp + def test_api_compile_2(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile('output') + if sys.platform != 'win32': + sofile = self.check_produced_files({ + 'output': {'mod_name_in_package': {'mymod.SO': None, + 'mymod.c': None, + 'mymod.o': None}}}) + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'output': {'mod_name_in_package': {'mymod.SO': None, + 'mymod.c': None}, + 'Release': '?'}}) + + @from_outside + def test_api_compile_3(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile(str(self.udir.join('foo'))) + if sys.platform != 'win32': + sofile = self.check_produced_files({ + 'foo': {'mod_name_in_package': {'mymod.SO': None, + 'mymod.c': None, + 'mymod.o': None}}}) + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'foo': {'mod_name_in_package': {'mymod.SO': None, + 'mymod.c': None}, + 'Release': '?'}}) + + @chdir_to_tmp + def test_api_compile_explicit_target_1(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile(target="foo.bar.*") + if sys.platform != 'win32': + sofile = self.check_produced_files({ + 'mod_name_in_package': {'foo.bar.SO': None, + 'mymod.c': None, + 'mymod.o': None}}) + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'mod_name_in_package': {'foo.bar.SO': None, + 'mymod.c': None}, + 'Release': '?'}) + + @chdir_to_tmp + def test_api_compile_explicit_target_3(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile(target="foo.bar.baz") + if sys.platform != 'win32': + self.check_produced_files({ + 'mod_name_in_package': {'foo.bar.baz': None, + 'mymod.c': None, + 'mymod.o': None}}) + sofile = os.path.join(str(self.udir), + 'mod_name_in_package', 'foo.bar.baz') + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'mod_name_in_package': {'foo.bar.baz': None, + 'mymod.c': None}, + 'Release': '?'}) + + @chdir_to_tmp + def test_api_distutils_extension_1(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + ext = ffi.distutils_extension() + self.check_produced_files({'build': { + 'mod_name_in_package': {'mymod.c': None}}}) + if hasattr(os.path, 'samefile'): + assert os.path.samefile(ext.sources[0], + 'build/mod_name_in_package/mymod.c') + + @from_outside + def test_api_distutils_extension_2(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + ext = ffi.distutils_extension(str(self.udir.join('foo'))) + self.check_produced_files({'foo': { + 'mod_name_in_package': {'mymod.c': None}}}) + if hasattr(os.path, 'samefile'): + assert os.path.samefile(ext.sources[0], + str(self.udir.join('foo/mod_name_in_package/mymod.c'))) + + + def _make_distutils_api(self): + os.mkdir("src") + os.mkdir(os.path.join("src", "pack1")) + with open(os.path.join("src", "pack1", "__init__.py"), "w") as f: + pass + with open("setup.py", "w") as f: + f.write("""if 1: + # https://bugs.python.org/issue23246 + import sys + if sys.platform == 'win32': + try: + import setuptools + except ImportError: + pass + + import cffi + ffi = cffi.FFI() + ffi.set_source("pack1.mymod", "/*code would be here*/") + + from distutils.core import setup + setup(name='example1', + version='0.1', + packages=['pack1'], + package_dir={'': 'src'}, + ext_modules=[ffi.distutils_extension()]) + """) + + @chdir_to_tmp + def test_distutils_api_1(self): + self._make_distutils_api() + self.run(["setup.py", "build"]) + self.check_produced_files({'setup.py': None, + 'build': '?', + 'src': {'pack1': {'__init__.py': None}}}) + + @chdir_to_tmp + def test_distutils_api_2(self): + self._make_distutils_api() + self.run(["setup.py", "build_ext", "-i"]) + self.check_produced_files({'setup.py': None, + 'build': '?', + 'src': {'pack1': {'__init__.py': None, + 'mymod.SO': None}}}) + + def _make_setuptools_abi(self): + self._prepare_setuptools() + os.mkdir("src0") + os.mkdir(os.path.join("src0", "pack2")) + with open(os.path.join("src0", "pack2", "__init__.py"), "w") as f: + pass + with open(os.path.join("src0", "pack2", "_build.py"), "w") as f: + f.write("""if 1: + import cffi + ffi = cffi.FFI() + ffi.set_source("pack2.mymod", None) + """) + with open("setup.py", "w") as f: + f.write("""if 1: + from setuptools import setup + setup(name='example1', + version='0.1', + packages=['pack2'], + package_dir={'': 'src0'}, + cffi_modules=["src0/pack2/_build.py:ffi"]) + """) + + @chdir_to_tmp + def test_setuptools_abi_1(self): + self._make_setuptools_abi() + self.run(["setup.py", "build"]) + self.check_produced_files({'setup.py': None, + 'build': '?', + 'src0': {'pack2': {'__init__.py': None, + '_build.py': None}}}) + + @chdir_to_tmp + def test_setuptools_abi_2(self): + self._make_setuptools_abi() + self.run(["setup.py", "build_ext", "-i"]) + self.check_produced_files({'setup.py': None, + 'src0': {'pack2': {'__init__.py': None, + '_build.py': None, + 'mymod.py': None}}}) + + def _make_setuptools_api(self): + self._prepare_setuptools() + os.mkdir("src1") + os.mkdir(os.path.join("src1", "pack3")) + with open(os.path.join("src1", "pack3", "__init__.py"), "w") as f: + pass + with open(os.path.join("src1", "pack3", "_build.py"), "w") as f: + f.write("""if 1: + import cffi + ffi = cffi.FFI() + ffi.set_source("pack3.mymod", "/*code would be here*/") + ffi._hi_there = 42 + """) + with open("setup.py", "w") as f: + f.write("from __future__ import print_function\n" + """if 1: + from setuptools import setup + from distutils.command.build_ext import build_ext + import os + + class TestBuildExt(build_ext): + def pre_run(self, ext, ffi): + print('_make_setuptools_api: in pre_run:', end=" ") + assert ffi._hi_there == 42 + assert ext.name == "pack3.mymod" + fn = os.path.join(os.path.dirname(self.build_lib), + '..', 'see_me') + print('creating %r' % (fn,)) + open(fn, 'w').close() + + setup(name='example1', + version='0.1', + packages=['pack3'], + package_dir={'': 'src1'}, + cffi_modules=["src1/pack3/_build.py:ffi"], + cmdclass={'build_ext': TestBuildExt}, + ) + """) + + @chdir_to_tmp + def test_setuptools_api_1(self): + self._make_setuptools_api() + self.run(["setup.py", "build"]) + self.check_produced_files({'setup.py': None, + 'build': '?', + 'see_me': None, + 'src1': {'pack3': {'__init__.py': None, + '_build.py': None}}}) + + @chdir_to_tmp + def test_setuptools_api_2(self): + self._make_setuptools_api() + self.run(["setup.py", "build_ext", "-i"]) + self.check_produced_files({'setup.py': None, + 'build': '?', + 'see_me': None, + 'src1': {'pack3': {'__init__.py': None, + '_build.py': None, + 'mymod.SO': None}}}) diff --git a/testing/embedding/__init__.py b/testing/embedding/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/embedding/add1-test.c b/testing/embedding/add1-test.c new file mode 100644 index 0000000..b9ede18 --- /dev/null +++ b/testing/embedding/add1-test.c @@ -0,0 +1,21 @@ +#include + +#ifdef _MSC_VER +#include +#endif + +extern int add1(int, int); + + +int main(void) +{ + int x, y; + x = add1(40, 2); + y = add1(100, -5); + printf("got: %d %d\n", x, y); +#ifdef _MSC_VER + if (x == 0 && y == 0) + Sleep(2000); +#endif + return 0; +} diff --git a/testing/embedding/add1.py b/testing/embedding/add1.py new file mode 100644 index 0000000..e5b3de1 --- /dev/null +++ b/testing/embedding/add1.py @@ -0,0 +1,33 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + import sys, time + sys.stdout.write("preparing") + for i in range(3): + sys.stdout.flush() + time.sleep(0.2) + sys.stdout.write(".") + sys.stdout.write("\n") + + from _add1_cffi import ffi + + int(ord("A")) # check that built-ins are there + + @ffi.def_extern() + def add1(x, y): + sys.stdout.write("adding %d and %d\n" % (x, y)) + sys.stdout.flush() + return x + y +""") + +ffi.set_source("_add1_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/add2-test.c b/testing/embedding/add2-test.c new file mode 100644 index 0000000..9620843 --- /dev/null +++ b/testing/embedding/add2-test.c @@ -0,0 +1,14 @@ +#include + +extern int add1(int, int); +extern int add2(int, int, int); + + +int main(void) +{ + int x, y; + x = add1(40, 2); + y = add2(100, -5, -20); + printf("got: %d %d\n", x, y); + return 0; +} diff --git a/testing/embedding/add2.py b/testing/embedding/add2.py new file mode 100644 index 0000000..311a464 --- /dev/null +++ b/testing/embedding/add2.py @@ -0,0 +1,29 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add2(int, int, int); +""") + +ffi.embedding_init_code(r""" + import sys + sys.stdout.write("prepADD2\n") + + assert '_add2_cffi' in sys.modules + m = sys.modules['_add2_cffi'] + import _add2_cffi + ffi = _add2_cffi.ffi + + @ffi.def_extern() + def add2(x, y, z): + sys.stdout.write("adding %d and %d and %d\n" % (x, y, z)) + sys.stdout.flush() + return x + y + z +""") + +ffi.set_source("_add2_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/add3.py b/testing/embedding/add3.py new file mode 100644 index 0000000..1361912 --- /dev/null +++ b/testing/embedding/add3.py @@ -0,0 +1,24 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add3(int, int, int, int); +""") + +ffi.embedding_init_code(r""" + from _add3_cffi import ffi + import sys + + @ffi.def_extern() + def add3(x, y, z, t): + sys.stdout.write("adding %d, %d, %d, %d\n" % (x, y, z, t)) + sys.stdout.flush() + return x + y + z + t +""") + +ffi.set_source("_add3_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/add_recursive-test.c b/testing/embedding/add_recursive-test.c new file mode 100644 index 0000000..cd29b79 --- /dev/null +++ b/testing/embedding/add_recursive-test.c @@ -0,0 +1,27 @@ +#include + +#ifdef _MSC_VER +# define DLLIMPORT __declspec(dllimport) +#else +# define DLLIMPORT extern +#endif + +DLLIMPORT int add_rec(int, int); +DLLIMPORT int (*my_callback)(int); + +static int some_callback(int x) +{ + printf("some_callback(%d)\n", x); + fflush(stdout); + return add_rec(x, 9); +} + +int main(void) +{ + int x, y; + my_callback = some_callback; + x = add_rec(40, 2); + y = add_rec(100, -5); + printf("got: %d %d\n", x, y); + return 0; +} diff --git a/testing/embedding/add_recursive.py b/testing/embedding/add_recursive.py new file mode 100644 index 0000000..9fa463d --- /dev/null +++ b/testing/embedding/add_recursive.py @@ -0,0 +1,33 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int (*my_callback)(int); + int add_rec(int, int); +""") + +ffi.embedding_init_code(r""" + from _add_recursive_cffi import ffi, lib + import sys + print("preparing REC") + sys.stdout.flush() + + @ffi.def_extern() + def add_rec(x, y): + print("adding %d and %d" % (x, y)) + sys.stdout.flush() + return x + y + + x = lib.my_callback(400) + print('<<< %d >>>' % (x,)) +""") + +ffi.set_source("_add_recursive_cffi", """ +/* use CFFI_DLLEXPORT: on windows, it expands to __declspec(dllexport), + which is needed to export a variable from a dll */ +CFFI_DLLEXPORT int (*my_callback)(int); +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/empty.py b/testing/embedding/empty.py new file mode 100644 index 0000000..aa8d830 --- /dev/null +++ b/testing/embedding/empty.py @@ -0,0 +1,10 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api("") + +ffi.set_source("_empty_cffi", "") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/initerror.py b/testing/embedding/initerror.py new file mode 100644 index 0000000..775cf56 --- /dev/null +++ b/testing/embedding/initerror.py @@ -0,0 +1,18 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + raise KeyError +""") + +ffi.set_source("_initerror_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) + diff --git a/testing/embedding/perf-test.c b/testing/embedding/perf-test.c new file mode 100644 index 0000000..2195bf5 --- /dev/null +++ b/testing/embedding/perf-test.c @@ -0,0 +1,90 @@ +#include +#include +#include +#ifdef PTEST_USE_THREAD +# include +static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; +static int remaining; +#endif + + +extern int add1(int, int); + + +static double time_delta(struct timeval *stop, struct timeval *start) +{ + return (stop->tv_sec - start->tv_sec) + + 1e-6 * (stop->tv_usec - start->tv_usec); +} + +static double measure(void) +{ + long long i, iterations; + int result; + struct timeval start, stop; + double elapsed; + + add1(0, 0); /* prepare off-line */ + + i = 0; + iterations = 1000; + result = gettimeofday(&start, NULL); + assert(result == 0); + + while (1) { + for (; i < iterations; i++) { + add1(((int)i) & 0xaaaaaa, ((int)i) & 0x555555); + } + result = gettimeofday(&stop, NULL); + assert(result == 0); + + elapsed = time_delta(&stop, &start); + assert(elapsed >= 0.0); + if (elapsed > 2.5) + break; + iterations = iterations * 3 / 2; + } + + return elapsed / (double)iterations; +} + +static void *start_routine(void *arg) +{ + double t = measure(); + printf("time per call: %.3g\n", t); + +#ifdef PTEST_USE_THREAD + pthread_mutex_lock(&mutex1); + remaining -= 1; + if (!remaining) + pthread_cond_signal(&cond1); + pthread_mutex_unlock(&mutex1); +#endif + + return arg; +} + + +int main(void) +{ +#ifndef PTEST_USE_THREAD + start_routine(0); +#else + pthread_t th; + int i, status; + + add1(0, 0); /* this is the main thread */ + + remaining = PTEST_USE_THREAD; + for (i = 0; i < PTEST_USE_THREAD; i++) { + status = pthread_create(&th, NULL, start_routine, NULL); + assert(status == 0); + } + pthread_mutex_lock(&mutex1); + while (remaining) + pthread_cond_wait(&cond1, &mutex1); + pthread_mutex_unlock(&mutex1); +#endif + return 0; +} diff --git a/testing/embedding/perf.py b/testing/embedding/perf.py new file mode 100644 index 0000000..a8d20f4 --- /dev/null +++ b/testing/embedding/perf.py @@ -0,0 +1,21 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + from _perf_cffi import ffi + + @ffi.def_extern() + def add1(x, y): + return x + y +""") + +ffi.set_source("_perf_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/test_basic.py b/testing/embedding/test_basic.py new file mode 100644 index 0000000..8463c3f --- /dev/null +++ b/testing/embedding/test_basic.py @@ -0,0 +1,207 @@ +import py +import sys, os, re +import shutil, subprocess, time +from testing.udir import udir +import cffi + + +local_dir = os.path.dirname(os.path.abspath(__file__)) +_link_error = '?' + +def check_lib_python_found(tmpdir): + global _link_error + if _link_error == '?': + ffi = cffi.FFI() + kwds = {} + ffi._apply_embedding_fix(kwds) + ffi.set_source("_test_lib_python_found", "", **kwds) + try: + ffi.compile(tmpdir=tmpdir, verbose=True) + except cffi.VerificationError as e: + _link_error = e + else: + _link_error = None + if _link_error: + py.test.skip(str(_link_error)) + + +def prefix_pythonpath(): + cffi_base = os.path.dirname(os.path.dirname(local_dir)) + pythonpath = org_env.get('PYTHONPATH', '').split(os.pathsep) + if cffi_base not in pythonpath: + pythonpath.insert(0, cffi_base) + return os.pathsep.join(pythonpath) + +def copy_away_env(): + global org_env + try: + org_env + except NameError: + org_env = os.environ.copy() + + +class EmbeddingTests: + _compiled_modules = {} + + def setup_method(self, meth): + check_lib_python_found(str(udir.ensure('embedding', dir=1))) + self._path = udir.join('embedding', meth.__name__) + if sys.platform == "win32" or sys.platform == "darwin": + self._compiled_modules.clear() # workaround + + def get_path(self): + return str(self._path.ensure(dir=1)) + + def _run_base(self, args, **kwds): + print('RUNNING:', args, kwds) + return subprocess.Popen(args, **kwds) + + def _run(self, args): + popen = self._run_base(args, cwd=self.get_path(), + stdout=subprocess.PIPE, + universal_newlines=True) + output = popen.stdout.read() + err = popen.wait() + if err: + raise OSError("popen failed with exit code %r: %r" % ( + err, args)) + print(output.rstrip()) + return output + + def prepare_module(self, name): + self.patch_environment() + if name not in self._compiled_modules: + path = self.get_path() + filename = '%s.py' % name + # NOTE: if you have an .egg globally installed with an older + # version of cffi, this will not work, because sys.path ends + # up with the .egg before the PYTHONPATH entries. I didn't + # find a solution to that: we could hack sys.path inside the + # script run here, but we can't hack it in the same way in + # execute(). + pathname = os.path.join(path, filename) + with open(pathname, 'w') as g: + g.write(''' +# https://bugs.python.org/issue23246 +import sys +if sys.platform == 'win32': + try: + import setuptools + except ImportError: + pass +''') + with open(os.path.join(local_dir, filename), 'r') as f: + g.write(f.read()) + + output = self._run([sys.executable, pathname]) + match = re.compile(r"\bFILENAME: (.+)").search(output) + assert match + dynamic_lib_name = match.group(1) + if sys.platform == 'win32': + assert dynamic_lib_name.endswith('_cffi.dll') + elif sys.platform == 'darwin': + assert dynamic_lib_name.endswith('_cffi.dylib') + else: + assert dynamic_lib_name.endswith('_cffi.so') + self._compiled_modules[name] = dynamic_lib_name + return self._compiled_modules[name] + + def compile(self, name, modules, opt=False, threads=False, defines={}): + path = self.get_path() + filename = '%s.c' % name + shutil.copy(os.path.join(local_dir, filename), path) + shutil.copy(os.path.join(local_dir, 'thread-test.h'), path) + import distutils.ccompiler + curdir = os.getcwd() + try: + os.chdir(self.get_path()) + c = distutils.ccompiler.new_compiler() + print('compiling %s with %r' % (name, modules)) + extra_preargs = [] + debug = True + if sys.platform == 'win32': + libfiles = [] + for m in modules: + m = os.path.basename(m) + assert m.endswith('.dll') + libfiles.append('Release\\%s.lib' % m[:-4]) + modules = libfiles + extra_preargs.append('/MANIFEST') + debug = False # you need to install extra stuff + # for this to work + elif threads: + extra_preargs.append('-pthread') + objects = c.compile([filename], macros=sorted(defines.items()), + debug=debug) + c.link_executable(objects + modules, name, extra_preargs=extra_preargs) + finally: + os.chdir(curdir) + + def patch_environment(self): + copy_away_env() + path = self.get_path() + # for libpypy-c.dll or Python27.dll + path = os.path.split(sys.executable)[0] + os.path.pathsep + path + env_extra = {'PYTHONPATH': prefix_pythonpath()} + if sys.platform == 'win32': + envname = 'PATH' + else: + envname = 'LD_LIBRARY_PATH' + libpath = org_env.get(envname) + if libpath: + libpath = path + os.path.pathsep + libpath + else: + libpath = path + env_extra[envname] = libpath + for key, value in sorted(env_extra.items()): + if os.environ.get(key) != value: + print('* setting env var %r to %r' % (key, value)) + os.environ[key] = value + + def execute(self, name): + path = self.get_path() + print('running %r in %r' % (name, path)) + executable_name = name + if sys.platform == 'win32': + executable_name = os.path.join(path, executable_name + '.exe') + else: + executable_name = os.path.join('.', executable_name) + popen = self._run_base([executable_name], cwd=path, + stdout=subprocess.PIPE, + universal_newlines=True) + result = popen.stdout.read() + err = popen.wait() + if err: + raise OSError("%r failed with exit code %r" % (name, err)) + return result + + +class TestBasic(EmbeddingTests): + def test_empty(self): + empty_cffi = self.prepare_module('empty') + + def test_basic(self): + add1_cffi = self.prepare_module('add1') + self.compile('add1-test', [add1_cffi]) + output = self.execute('add1-test') + assert output == ("preparing...\n" + "adding 40 and 2\n" + "adding 100 and -5\n" + "got: 42 95\n") + + def test_two_modules(self): + add1_cffi = self.prepare_module('add1') + add2_cffi = self.prepare_module('add2') + self.compile('add2-test', [add1_cffi, add2_cffi]) + output = self.execute('add2-test') + assert output == ("preparing...\n" + "adding 40 and 2\n" + "prepADD2\n" + "adding 100 and -5 and -20\n" + "got: 42 75\n") + + def test_init_time_error(self): + initerror_cffi = self.prepare_module('initerror') + self.compile('add1-test', [initerror_cffi]) + output = self.execute('add1-test') + assert output == "got: 0 0\n" # plus lots of info to stderr diff --git a/testing/embedding/test_performance.py b/testing/embedding/test_performance.py new file mode 100644 index 0000000..f9f2605 --- /dev/null +++ b/testing/embedding/test_performance.py @@ -0,0 +1,52 @@ +import sys +from testing.embedding.test_basic import EmbeddingTests + +if sys.platform == 'win32': + import py + py.test.skip("written with POSIX functions") + + +class TestPerformance(EmbeddingTests): + def test_perf_single_threaded(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_1_thread(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '1'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_2_threads(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '2'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_4_threads(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '4'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_8_threads(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '8'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) diff --git a/testing/embedding/test_recursive.py b/testing/embedding/test_recursive.py new file mode 100644 index 0000000..b85e7ed --- /dev/null +++ b/testing/embedding/test_recursive.py @@ -0,0 +1,15 @@ +from testing.embedding.test_basic import EmbeddingTests + + +class TestRecursive(EmbeddingTests): + def test_recursive(self): + add_recursive_cffi = self.prepare_module('add_recursive') + self.compile('add_recursive-test', [add_recursive_cffi]) + output = self.execute('add_recursive-test') + assert output == ("preparing REC\n" + "some_callback(400)\n" + "adding 400 and 9\n" + "<<< 409 >>>\n" + "adding 40 and 2\n" + "adding 100 and -5\n" + "got: 42 95\n") diff --git a/testing/embedding/test_thread.py b/testing/embedding/test_thread.py new file mode 100644 index 0000000..1895076 --- /dev/null +++ b/testing/embedding/test_thread.py @@ -0,0 +1,61 @@ +from testing.embedding.test_basic import EmbeddingTests + + +class TestThread(EmbeddingTests): + def test_first_calls_in_parallel(self): + add1_cffi = self.prepare_module('add1') + self.compile('thread1-test', [add1_cffi], threads=True) + for i in range(20): + output = self.execute('thread1-test') + assert output == ("starting\n" + "preparing...\n" + + "adding 40 and 2\n" * 10 + + "done\n") + + def _take_out(self, text, content): + assert content in text + i = text.index(content) + return text[:i] + text[i+len(content):] + + def test_init_different_modules_in_different_threads(self): + add1_cffi = self.prepare_module('add1') + add2_cffi = self.prepare_module('add2') + self.compile('thread2-test', [add1_cffi, add2_cffi], threads=True) + output = self.execute('thread2-test') + output = self._take_out(output, "preparing") + output = self._take_out(output, ".") + output = self._take_out(output, ".") + # at least the 3rd dot should be after everything from ADD2 + assert output == ("starting\n" + "prepADD2\n" + "adding 1000 and 200 and 30\n" + ".\n" + "adding 40 and 2\n" + "done\n") + + def test_alt_issue(self): + add1_cffi = self.prepare_module('add1') + add2_cffi = self.prepare_module('add2') + self.compile('thread2-test', [add1_cffi, add2_cffi], + threads=True, defines={'T2TEST_AGAIN_ADD1': '1'}) + output = self.execute('thread2-test') + output = self._take_out(output, "adding 40 and 2\n") + assert output == ("starting\n" + "preparing...\n" + "adding -1 and -1\n" + "prepADD2\n" + "adding 1000 and 200 and 30\n" + "done\n") + + def test_load_in_parallel_more(self): + add2_cffi = self.prepare_module('add2') + add3_cffi = self.prepare_module('add3') + self.compile('thread3-test', [add2_cffi, add3_cffi], threads=True) + for i in range(150): + output = self.execute('thread3-test') + for j in range(10): + output = self._take_out(output, "adding 40 and 2 and 100\n") + output = self._take_out(output, "adding 1000, 200, 30, 4\n") + assert output == ("starting\n" + "prepADD2\n" + "done\n") diff --git a/testing/embedding/test_tlocal.py b/testing/embedding/test_tlocal.py new file mode 100644 index 0000000..6e7c5af --- /dev/null +++ b/testing/embedding/test_tlocal.py @@ -0,0 +1,10 @@ +from testing.embedding.test_basic import EmbeddingTests + + +class TestThreadLocal(EmbeddingTests): + def test_thread_local(self): + tlocal_cffi = self.prepare_module('tlocal') + self.compile('tlocal-test', [tlocal_cffi], threads=True) + for i in range(10): + output = self.execute('tlocal-test') + assert output == "done\n" diff --git a/testing/embedding/thread-test.h b/testing/embedding/thread-test.h new file mode 100644 index 0000000..f66cf70 --- /dev/null +++ b/testing/embedding/thread-test.h @@ -0,0 +1,96 @@ +/************************************************************/ +#ifndef _MSC_VER +/************************************************************/ + + +#include + +/* don't include , it is not available on OS/X */ + +typedef struct { + pthread_mutex_t mutex1; + pthread_cond_t cond1; + unsigned int value; +} sem_t; + +static int sem_init(sem_t *sem, int pshared, unsigned int value) +{ + assert(pshared == 0); + sem->value = value; + return (pthread_mutex_init(&sem->mutex1, NULL) || + pthread_cond_init(&sem->cond1, NULL)); +} + +static int sem_post(sem_t *sem) +{ + pthread_mutex_lock(&sem->mutex1); + sem->value += 1; + pthread_cond_signal(&sem->cond1); + pthread_mutex_unlock(&sem->mutex1); + return 0; +} + +static int sem_wait(sem_t *sem) +{ + pthread_mutex_lock(&sem->mutex1); + while (sem->value == 0) + pthread_cond_wait(&sem->cond1, &sem->mutex1); + sem->value -= 1; + pthread_mutex_unlock(&sem->mutex1); + return 0; +} + + +/************************************************************/ +#else +/************************************************************/ + + +/* Very quick and dirty, just what I need for these tests. + Don't use directly in any real code! +*/ + +#include +#include + +typedef HANDLE sem_t; +typedef HANDLE pthread_t; + +static int sem_init(sem_t *sem, int pshared, unsigned int value) +{ + assert(pshared == 0); + assert(value == 0); + *sem = CreateSemaphore(NULL, 0, 999, NULL); + return *sem ? 0 : -1; +} + +static int sem_post(sem_t *sem) +{ + return ReleaseSemaphore(*sem, 1, NULL) ? 0 : -1; +} + +static int sem_wait(sem_t *sem) +{ + WaitForSingleObject(*sem, INFINITE); + return 0; +} + +static DWORD WINAPI myThreadProc(LPVOID lpParameter) +{ + void *(* start_routine)(void *) = (void *(*)(void *))lpParameter; + start_routine(NULL); + return 0; +} + +static int pthread_create(pthread_t *thread, void *attr, + void *start_routine(void *), void *arg) +{ + assert(arg == NULL); + *thread = CreateThread(NULL, 0, myThreadProc, start_routine, 0, NULL); + return *thread ? 0 : -1; +} + + +/************************************************************/ +#endif +/************************************************************/ diff --git a/testing/embedding/thread1-test.c b/testing/embedding/thread1-test.c new file mode 100644 index 0000000..70bb861 --- /dev/null +++ b/testing/embedding/thread1-test.c @@ -0,0 +1,43 @@ +#include +#include +#include "thread-test.h" + +#define NTHREADS 10 + + +extern int add1(int, int); + +static sem_t done; + + +static void *start_routine(void *arg) +{ + int x, status; + x = add1(40, 2); + assert(x == 42); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + printf("starting\n"); + fflush(stdout); + for (i = 0; i < NTHREADS; i++) { + status = pthread_create(&th, NULL, start_routine, NULL); + assert(status == 0); + } + for (i = 0; i < NTHREADS; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/testing/embedding/thread2-test.c b/testing/embedding/thread2-test.c new file mode 100644 index 0000000..62f5ec8 --- /dev/null +++ b/testing/embedding/thread2-test.c @@ -0,0 +1,57 @@ +#include +#include +#include "thread-test.h" + +extern int add1(int, int); +extern int add2(int, int, int); + +static sem_t done; + + +static void *start_routine_1(void *arg) +{ + int x, status; + x = add1(40, 2); + assert(x == 42); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +static void *start_routine_2(void *arg) +{ + int x, status; +#ifdef T2TEST_AGAIN_ADD1 + add1(-1, -1); +#endif + x = add2(1000, 200, 30); + assert(x == 1230); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + printf("starting\n"); + fflush(stdout); + status = pthread_create(&th, NULL, start_routine_1, NULL); + assert(status == 0); + status = pthread_create(&th, NULL, start_routine_2, NULL); + assert(status == 0); + + for (i = 0; i < 2; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/testing/embedding/thread3-test.c b/testing/embedding/thread3-test.c new file mode 100644 index 0000000..69ada27 --- /dev/null +++ b/testing/embedding/thread3-test.c @@ -0,0 +1,56 @@ +#include +#include +#include "thread-test.h" + +extern int add2(int, int, int); +extern int add3(int, int, int, int); + +static sem_t done; + + +static void *start_routine_2(void *arg) +{ + int x, status; + x = add2(40, 2, 100); + assert(x == 142); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +static void *start_routine_3(void *arg) +{ + int x, status; + x = add3(1000, 200, 30, 4); + assert(x == 1234); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + printf("starting\n"); + fflush(stdout); + for (i = 0; i < 10; i++) { + status = pthread_create(&th, NULL, start_routine_2, NULL); + assert(status == 0); + status = pthread_create(&th, NULL, start_routine_3, NULL); + assert(status == 0); + } + for (i = 0; i < 20; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + fflush(stdout); /* this is occasionally needed on Windows */ + return 0; +} diff --git a/testing/embedding/tlocal-test.c b/testing/embedding/tlocal-test.c new file mode 100644 index 0000000..b78a03d --- /dev/null +++ b/testing/embedding/tlocal-test.c @@ -0,0 +1,47 @@ +#include +#include +#include "thread-test.h" + +#define NTHREADS 10 + + +extern int add1(int, int); + +static sem_t done; + + +static void *start_routine(void *arg) +{ + int i, x, expected, status; + + expected = add1(40, 2); + assert((expected % 1000) == 42); + + for (i=0; i<10; i++) { + x = add1(50, i); + assert(x == expected + 8 + i); + } + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + for (i = 0; i < NTHREADS; i++) { + status = pthread_create(&th, NULL, start_routine, NULL); + assert(status == 0); + } + for (i = 0; i < NTHREADS; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/testing/embedding/tlocal.py b/testing/embedding/tlocal.py new file mode 100644 index 0000000..7800dff --- /dev/null +++ b/testing/embedding/tlocal.py @@ -0,0 +1,33 @@ +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + from _tlocal_cffi import ffi + import itertools + try: + import thread + g_seen = itertools.count().next + except ImportError: + import _thread as thread # py3 + g_seen = itertools.count().__next__ + tloc = thread._local() + + @ffi.def_extern() + def add1(x, y): + try: + num = tloc.num + except AttributeError: + num = tloc.num = g_seen() * 1000 + return x + y + num +""") + +ffi.set_source("_tlocal_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/testing/support.py b/testing/support.py new file mode 100644 index 0000000..65f010c --- /dev/null +++ b/testing/support.py @@ -0,0 +1,88 @@ +import sys + +if sys.version_info < (3,): + __all__ = ['u'] + + class U(object): + def __add__(self, other): + return eval('u'+repr(other).replace(r'\\u', r'\u') + .replace(r'\\U', r'\U')) + u = U() + long = long # for further "from testing.support import long" + assert u+'a\x00b' == eval(r"u'a\x00b'") + assert u+'a\u1234b' == eval(r"u'a\u1234b'") + assert u+'a\U00012345b' == eval(r"u'a\U00012345b'") + +else: + __all__ = ['u', 'unicode', 'long'] + u = "" + unicode = str + long = int + + +class StdErrCapture(object): + """Capture writes to sys.stderr (not to the underlying file descriptor).""" + def __enter__(self): + try: + from StringIO import StringIO + except ImportError: + from io import StringIO + self.old_stderr = sys.stderr + sys.stderr = f = StringIO() + return f + def __exit__(self, *args): + sys.stderr = self.old_stderr + + +class FdWriteCapture(object): + """xxx limited to capture at most 512 bytes of output, according + to the Posix manual.""" + + def __init__(self, capture_fd=2): # stderr by default + if sys.platform == 'win32': + import py + py.test.skip("seems not to work, too bad") + self.capture_fd = capture_fd + + def __enter__(self): + import os + self.read_fd, self.write_fd = os.pipe() + self.copy_fd = os.dup(self.capture_fd) + os.dup2(self.write_fd, self.capture_fd) + return self + + def __exit__(self, *args): + import os + os.dup2(self.copy_fd, self.capture_fd) + os.close(self.copy_fd) + os.close(self.write_fd) + self._value = os.read(self.read_fd, 512) + os.close(self.read_fd) + + def getvalue(self): + return self._value + +def _verify(ffi, module_name, preamble, *args, **kwds): + import imp + from cffi.recompiler import recompile + from .udir import udir + assert module_name not in sys.modules, "module name conflict: %r" % ( + module_name,) + kwds.setdefault('tmpdir', str(udir)) + outputfilename = recompile(ffi, module_name, preamble, *args, **kwds) + module = imp.load_dynamic(module_name, outputfilename) + # + # hack hack hack: copy all *bound methods* from module.ffi back to the + # ffi instance. Then calls like ffi.new() will invoke module.ffi.new(). + for name in dir(module.ffi): + if not name.startswith('_'): + attr = getattr(module.ffi, name) + if attr is not getattr(ffi, name, object()): + setattr(ffi, name, attr) + def typeof_disabled(*args, **kwds): + raise NotImplementedError + ffi._typeof = typeof_disabled + for name in dir(ffi): + if not name.startswith('_') and not hasattr(module.ffi, name): + setattr(ffi, name, NotImplemented) + return module.lib diff --git a/testing/udir.py b/testing/udir.py new file mode 100644 index 0000000..4dd0a11 --- /dev/null +++ b/testing/udir.py @@ -0,0 +1,13 @@ +import py +import sys + +udir = py.path.local.make_numbered_dir(prefix = 'ffi-') + + +# Windows-only workaround for some configurations: see +# https://bugs.python.org/issue23246 (Python 2.7.9) +if sys.platform == 'win32': + try: + import setuptools + except ImportError: + pass -- cgit v1.2.3