Author: Armin Rigo <[email protected]>
Branch:
Changeset: r2200:bdd39fccaf33
Date: 2015-07-03 11:37 +0200
http://bitbucket.org/cffi/cffi/changeset/bdd39fccaf33/
Log: Refactor the way global variables are accessed. Now, every access
to "lib.var" calls a generated C function to get "&var". This fixes
issue #212 and also make it work in more cases, like __thread
variables.
diff --git a/c/cglob.c b/c/cglob.c
--- a/c/cglob.c
+++ b/c/cglob.c
@@ -1,9 +1,12 @@
+
+typedef void *(*gs_fetch_addr_fn)(void);
typedef struct {
PyObject_HEAD
CTypeDescrObject *gs_type;
char *gs_data;
+ gs_fetch_addr_fn gs_fetch_addr;
} GlobSupportObject;
@@ -38,7 +41,8 @@
#define GlobSupport_Check(ob) (Py_TYPE(ob) == &GlobSupport_Type)
-static PyObject *make_global_var(CTypeDescrObject *type, char *addr)
+static PyObject *make_global_var(CTypeDescrObject *type, char *addr,
+ gs_fetch_addr_fn fetch_addr)
{
GlobSupportObject *gs = PyObject_New(GlobSupportObject, &GlobSupport_Type);
if (gs == NULL)
@@ -47,26 +51,47 @@
Py_INCREF(type);
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
+ }
+ return data;
+}
+
static PyObject *read_global_var(GlobSupportObject *gs)
{
- return convert_to_object(gs->gs_data, gs->gs_type);
+ void *data = fetch_global_var_addr(gs);
+ return convert_to_object(data, gs->gs_type);
}
static int write_global_var(GlobSupportObject *gs, PyObject *obj)
{
- return convert_from_object(gs->gs_data, gs->gs_type, obj);
+ void *data = fetch_global_var_addr(gs);
+ 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;
- x = new_simple_cdata(gs->gs_data, (CTypeDescrObject *)ptrtype);
+ data = fetch_global_var_addr(gs);
+ x = new_simple_cdata(data, (CTypeDescrObject *)ptrtype);
Py_DECREF(ptrtype);
return x;
}
diff --git a/c/lib_obj.c b/c/lib_obj.c
--- a/c/lib_obj.c
+++ b/c/lib_obj.c
@@ -267,7 +267,8 @@
return NULL;
if (ct->ct_size <= 0) {
- PyErr_SetString(PyExc_SystemError, "constant has no known size");
+ PyErr_Format(FFIError, "constant '%s' is of type '%s', "
+ "whose size is not known", s, ct->ct_name);
return NULL;
}
if (g->address == NULL) {
@@ -299,7 +300,10 @@
case _CFFI_OP_GLOBAL_VAR:
{
- /* global variable of the exact type specified here */
+ /* 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));
@@ -320,12 +324,21 @@
if (address == NULL)
return NULL;
}
- x = make_global_var(ct, address);
+ x = make_global_var(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(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
@@ -378,22 +391,25 @@
} \
} while (0)
-static PyObject *_lib_dir1(LibObject *lib, int ignore_type)
+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 *lst = PyList_New(total);
+ PyObject *s, *lst = PyList_New(total);
if (lst == NULL)
return NULL;
for (i = 0; i < total; i++) {
- if (_CFFI_GETOP(g[i].type_op) != ignore_type) {
- PyObject *s = PyText_FromString(g[i].name);
- if (s == NULL)
- goto error;
- PyList_SET_ITEM(lst, count, s);
- count++;
+ 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;
@@ -445,7 +461,7 @@
missing:
if (strcmp(PyText_AsUTF8(name), "__all__") == 0) {
PyErr_Clear();
- return _lib_dir1(lib, _CFFI_OP_GLOBAL_VAR);
+ return _lib_dir1(lib, 1);
}
if (strcmp(PyText_AsUTF8(name), "__dict__") == 0) {
PyErr_Clear();
@@ -481,7 +497,7 @@
static PyObject *lib_dir(PyObject *self, PyObject *noarg)
{
- return _lib_dir1((LibObject *)self, -1);
+ return _lib_dir1((LibObject *)self, 0);
}
static PyMethodDef lib_methods[] = {
diff --git a/cffi/cffi_opcode.py b/cffi/cffi_opcode.py
--- a/cffi/cffi_opcode.py
+++ b/cffi/cffi_opcode.py
@@ -53,6 +53,7 @@
OP_GLOBAL_VAR = 33
OP_DLOPEN_FUNC = 35
OP_DLOPEN_CONST = 37
+OP_GLOBAL_VAR_F = 39
PRIM_VOID = 0
PRIM_BOOL = 1
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -35,9 +35,6 @@
def is_integer_type(self):
return False
- def sizeof_enabled(self):
- return False
-
def get_cached_btype(self, ffi, finishlist, can_delay=False):
try:
BType = ffi._cached_btypes[self]
@@ -80,8 +77,7 @@
class BasePrimitiveType(BaseType):
- def sizeof_enabled(self):
- return True
+ pass
class PrimitiveType(BasePrimitiveType):
@@ -205,9 +201,6 @@
class FunctionPtrType(BaseFunctionType):
_base_pattern = '(*&)(%s)'
- def sizeof_enabled(self):
- return True
-
def build_backend_type(self, ffi, finishlist):
result = self.result.get_cached_btype(ffi, finishlist)
args = []
@@ -233,9 +226,6 @@
extra = self._base_pattern
self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra)
- def sizeof_enabled(self):
- return True
-
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)
@@ -276,9 +266,6 @@
self.c_name_with_marker = (
self.item.c_name_with_marker.replace('&', brackets))
- def sizeof_enabled(self):
- return self.item.sizeof_enabled() and self.length is not None
-
def resolve_length(self, newlength):
return ArrayType(self.item, newlength)
@@ -433,9 +420,6 @@
from . import ffiplatform
raise ffiplatform.VerificationMissing(self._get_c_name())
- def sizeof_enabled(self):
- return self.fldtypes is not None
-
def build_backend_type(self, ffi, finishlist):
self.check_not_partial()
finishlist.append(self)
@@ -464,9 +448,6 @@
self.baseinttype = baseinttype
self.build_c_name_with_marker()
- def sizeof_enabled(self):
- return True # not strictly true, but external enums are obscure
-
def force_the_name(self, forcename):
StructOrUnionOrEnum.force_the_name(self, forcename)
if self.forcename is None:
diff --git a/cffi/parse_c_type.h b/cffi/parse_c_type.h
--- a/cffi/parse_c_type.h
+++ b/cffi/parse_c_type.h
@@ -26,6 +26,7 @@
#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_PRIM_VOID 0
#define _CFFI_PRIM_BOOL 1
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -981,10 +981,6 @@
if not self.target_is_python and tp.is_integer_type():
type_op = CffiOp(OP_CONSTANT_INT, -1)
else:
- if not tp.sizeof_enabled():
- raise ffiplatform.VerificationError(
- "constant '%s' is of type '%s', whose size is not known"
- % (name, tp._get_c_name()))
if self.target_is_python:
const_kind = OP_DLOPEN_CONST
else:
@@ -1069,18 +1065,36 @@
self._do_collect_type(self._global_type(tp, name))
def _generate_cpy_variable_decl(self, tp, name):
- pass
+ 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.)
+ prnt('static ' + tp.get_c_name('*_cffi_var_%s(void)' % (name,)))
+ 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]
- type_op = CffiOp(OP_GLOBAL_VAR, type_index)
- if tp.sizeof_enabled():
- size = "sizeof(%s)" % (name,)
+ if self.target_is_python:
+ op = OP_GLOBAL_VAR
else:
- size = 0
+ op = OP_GLOBAL_VAR_F
self._lsts["global"].append(
- GlobalExpr(name, '&%s' % name, type_op, size))
+ GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index)))
# ----------
# emitting the opcodes for individual types
diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst
--- a/doc/source/cdef.rst
+++ b/doc/source/cdef.rst
@@ -562,8 +562,6 @@
foo_wrapper(struct my_complex c) { foo(c.real + c.imag*1j); }``, and
call ``foo_wrapper`` rather than ``foo`` directly.
-* Thread-local variables (access them via getter/setter functions)
-
* Function pointers with non-default calling conventions (e.g. on
Windows, "stdcall").
@@ -577,6 +575,11 @@
get reduced safety checks: for example, you risk overwriting the
following fields by passing too many array items in the constructor.
+.. versionadded:: 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.
+
Debugging dlopen'ed C libraries
-------------------------------
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -30,6 +30,12 @@
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
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -511,22 +511,38 @@
def test_bad_size_of_global_1():
ffi = FFI()
ffi.cdef("short glob;")
- lib = verify(ffi, "test_bad_size_of_global_1", "long glob;")
- py.test.raises(ffi.error, "lib.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];")
- lib = verify(ffi, "test_bad_size_of_global_2", "int glob[9];")
- e = py.test.raises(ffi.error, "lib.glob")
- assert str(e.value) == ("global variable 'glob' should be 40 bytes "
- "according to the cdef, but is actually 36")
+ py.test.raises(VerificationError, verify, ffi,
+ "test_bad_size_of_global_2", "int glob[9];")
-def test_unspecified_size_of_global():
+def test_unspecified_size_of_global_1():
ffi = FFI()
ffi.cdef("int glob[];")
- lib = verify(ffi, "test_unspecified_size_of_global", "int glob[10];")
- lib.glob # does not crash
+ 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()
@@ -888,9 +904,11 @@
typedef ... opaque_t;
const opaque_t CONSTANT;
""")
- e = py.test.raises(VerificationError, verify, ffi,
- 'test_constant_of_unknown_size', "stuff")
- assert str(e.value) == ("constant CONSTANT: constant 'CONSTANT' is of "
+ 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():
@@ -899,7 +917,7 @@
typedef ... opaque_t;
opaque_t globvar;
""")
- lib = verify(ffi, 'test_constant_of_unknown_size', """
+ lib = verify(ffi, 'test_variable_of_unknown_size', """
typedef char opaque_t[6];
opaque_t globvar = "hello";
""")
@@ -1081,3 +1099,32 @@
assert hasattr(lib, '__dict__')
assert lib.__all__ == ['MYFOO', 'mybar'] # but not 'myvar'
assert lib.__name__ == repr(lib)
+
+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():
+ return values + it.next()
+ 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
diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py
--- a/testing/cffi1/test_verify1.py
+++ b/testing/cffi1/test_verify1.py
@@ -2242,3 +2242,27 @@
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]
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit