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

Reply via email to