Commits

mattip committed 4b70a2b Merge

merge default into branch

Comments (0)

Files changed (5)

c/_cffi_backend.c

 # else
 #  include "misc_thread.h"
 # endif
+# define save_errno_only      save_errno
+# define restore_errno_only   restore_errno
 #endif
 
 #ifdef HAVE_WCHAR_H
     else if (ct->ct_flags & (CT_POINTER|CT_ARRAY|CT_FUNCTIONPTR)) {
         return &ffi_type_pointer;
     }
-    else if (ct->ct_flags & CT_VOID) {
+    else if ((ct->ct_flags & CT_VOID) && is_result_type) {
         return &ffi_type_void;
     }
 
 static PyObject *b_get_errno(PyObject *self, PyObject *noarg)
 {
     int err;
-    restore_errno();
+    restore_errno_only();
     err = errno;
     errno = 0;
     return PyInt_FromLong(err);
     if (!PyArg_ParseTuple(args, "i:set_errno", &i))
         return NULL;
     errno = i;
-    save_errno();
+    save_errno_only();
     errno = 0;
     Py_INCREF(Py_None);
     return Py_None;
     /* else: cannot report the error */
 }
 
+static void save_errno_only(void)
+{
+    int current_err = errno;
+    struct cffi_errno_s *p;
+
+    p = _geterrno_object();
+    if (p != NULL) {
+        p->saved_errno = current_err;
+    }
+    /* else: cannot report the error */
+}
+
 static void restore_errno(void)
 {
     struct cffi_errno_s *p;
     /* else: cannot report the error */
 }
 
+static void restore_errno_only(void)
+{
+    struct cffi_errno_s *p;
+
+    p = _geterrno_object();
+    if (p != NULL) {
+        errno = p->saved_errno;
+    }
+    /* else: cannot report the error */
+}
 
 /************************************************************/
 /* Emulate dlopen()&co. from the Windows API */
     BFunc = new_function_type((BInt, BInt), BVoid, False)
     assert repr(BFunc) == "<ctype 'void(*)(int, int)'>"
 
+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)
 
 from . import api, model
-import pycparser, weakref, re
+import pycparser.c_parser, weakref, re
 
 _r_comment = re.compile(r"/\*.*?\*/|//.*?$", re.DOTALL | re.MULTILINE)
 _r_define  = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)\s+(.*?)$",
         csource, macros = _preprocess(csource)
         csourcelines.append(csource)
         csource = '\n'.join(csourcelines)
-        ast = _get_parser().parse(csource)
+        try:
+            ast = _get_parser().parse(csource)
+        except pycparser.c_parser.ParseError, e:
+            self.convert_pycparser_error(e, csource)
         return ast, macros
 
+    def convert_pycparser_error(self, e, csource):
+        # xxx look for ":NUM:" at the start of str(e) and try to interpret
+        # it as a line number
+        line = None
+        msg = str(e)
+        if msg.startswith(':') and ':' in msg[1:]:
+            linenum = msg[1:msg.find(':',1)]
+            if linenum.isdigit():
+                linenum = int(linenum, 10)
+                csourcelines = csource.splitlines()
+                if 1 <= linenum <= len(csourcelines):
+                    line = csourcelines[linenum-1]
+        if line:
+            msg = 'cannot parse "%s"\n%s' % (line, msg)
+        else:
+            msg = 'parse error\n%s' % (msg,)
+        raise api.CDefError(msg)
+
     def parse(self, csource, override=False):
         prev_override = self._override
         try:
             params[-1].type.type.names == ['__dotdotdot__'])
         if ellipsis:
             params.pop()
-        if (len(params) == 1 and
+            if not params:
+                raise api.CDefError(
+                    "%s: a function with only '(...)' as argument"
+                    " is not correct C" % (funcname or 'in expression'))
+        elif (len(params) == 1 and
             isinstance(params[0].type, pycparser.c_ast.TypeDecl) and
             isinstance(params[0].type.type, pycparser.c_ast.IdentifierType)
                 and list(params[0].type.type.names) == ['void']):

testing/test_parsing.py

-import py, sys
+import py, sys, re
 from cffi import FFI, FFIError, CDefError, VerificationError
 
 class FakeBackend(object):
     assert C.foo.BType == '<func (), <int>, False>'
     ffi.cdef("long foo(void);", override=True)
     assert C.foo.BType == '<func (), <long>, 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) == \
+           "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 re.match(r'cannot parse " x y z "\n:\d+:', str(e.value))