• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import types
2import weakref
3
4from .lock import allocate_lock
5from .error import CDefError, VerificationError, VerificationMissing
6
7# type qualifiers
8Q_CONST    = 0x01
9Q_RESTRICT = 0x02
10Q_VOLATILE = 0x04
11
12def qualify(quals, replace_with):
13    if quals & Q_CONST:
14        replace_with = ' const ' + replace_with.lstrip()
15    if quals & Q_VOLATILE:
16        replace_with = ' volatile ' + replace_with.lstrip()
17    if quals & Q_RESTRICT:
18        # It seems that __restrict is supported by gcc and msvc.
19        # If you hit some different compiler, add a #define in
20        # _cffi_include.h for it (and in its copies, documented there)
21        replace_with = ' __restrict ' + replace_with.lstrip()
22    return replace_with
23
24
25class BaseTypeByIdentity(object):
26    is_array_type = False
27    is_raw_function = False
28
29    def get_c_name(self, replace_with='', context='a C file', quals=0):
30        result = self.c_name_with_marker
31        assert result.count('&') == 1
32        # some logic duplication with ffi.getctype()... :-(
33        replace_with = replace_with.strip()
34        if replace_with:
35            if replace_with.startswith('*') and '&[' in result:
36                replace_with = '(%s)' % replace_with
37            elif not replace_with[0] in '[(':
38                replace_with = ' ' + replace_with
39        replace_with = qualify(quals, replace_with)
40        result = result.replace('&', replace_with)
41        if '$' in result:
42            raise VerificationError(
43                "cannot generate '%s' in %s: unknown type name"
44                % (self._get_c_name(), context))
45        return result
46
47    def _get_c_name(self):
48        return self.c_name_with_marker.replace('&', '')
49
50    def has_c_name(self):
51        return '$' not in self._get_c_name()
52
53    def is_integer_type(self):
54        return False
55
56    def get_cached_btype(self, ffi, finishlist, can_delay=False):
57        try:
58            BType = ffi._cached_btypes[self]
59        except KeyError:
60            BType = self.build_backend_type(ffi, finishlist)
61            BType2 = ffi._cached_btypes.setdefault(self, BType)
62            assert BType2 is BType
63        return BType
64
65    def __repr__(self):
66        return '<%s>' % (self._get_c_name(),)
67
68    def _get_items(self):
69        return [(name, getattr(self, name)) for name in self._attrs_]
70
71
72class BaseType(BaseTypeByIdentity):
73
74    def __eq__(self, other):
75        return (self.__class__ == other.__class__ and
76                self._get_items() == other._get_items())
77
78    def __ne__(self, other):
79        return not self == other
80
81    def __hash__(self):
82        return hash((self.__class__, tuple(self._get_items())))
83
84
85class VoidType(BaseType):
86    _attrs_ = ()
87
88    def __init__(self):
89        self.c_name_with_marker = 'void&'
90
91    def build_backend_type(self, ffi, finishlist):
92        return global_cache(self, ffi, 'new_void_type')
93
94void_type = VoidType()
95
96
97class BasePrimitiveType(BaseType):
98    def is_complex_type(self):
99        return False
100
101
102class PrimitiveType(BasePrimitiveType):
103    _attrs_ = ('name',)
104
105    ALL_PRIMITIVE_TYPES = {
106        'char':               'c',
107        'short':              'i',
108        'int':                'i',
109        'long':               'i',
110        'long long':          'i',
111        'signed char':        'i',
112        'unsigned char':      'i',
113        'unsigned short':     'i',
114        'unsigned int':       'i',
115        'unsigned long':      'i',
116        'unsigned long long': 'i',
117        'float':              'f',
118        'double':             'f',
119        'long double':        'f',
120        'float _Complex':     'j',
121        'double _Complex':    'j',
122        '_Bool':              'i',
123        # the following types are not primitive in the C sense
124        'wchar_t':            'c',
125        'char16_t':           'c',
126        'char32_t':           'c',
127        'int8_t':             'i',
128        'uint8_t':            'i',
129        'int16_t':            'i',
130        'uint16_t':           'i',
131        'int32_t':            'i',
132        'uint32_t':           'i',
133        'int64_t':            'i',
134        'uint64_t':           'i',
135        'int_least8_t':       'i',
136        'uint_least8_t':      'i',
137        'int_least16_t':      'i',
138        'uint_least16_t':     'i',
139        'int_least32_t':      'i',
140        'uint_least32_t':     'i',
141        'int_least64_t':      'i',
142        'uint_least64_t':     'i',
143        'int_fast8_t':        'i',
144        'uint_fast8_t':       'i',
145        'int_fast16_t':       'i',
146        'uint_fast16_t':      'i',
147        'int_fast32_t':       'i',
148        'uint_fast32_t':      'i',
149        'int_fast64_t':       'i',
150        'uint_fast64_t':      'i',
151        'intptr_t':           'i',
152        'uintptr_t':          'i',
153        'intmax_t':           'i',
154        'uintmax_t':          'i',
155        'ptrdiff_t':          'i',
156        'size_t':             'i',
157        'ssize_t':            'i',
158        }
159
160    def __init__(self, name):
161        assert name in self.ALL_PRIMITIVE_TYPES
162        self.name = name
163        self.c_name_with_marker = name + '&'
164
165    def is_char_type(self):
166        return self.ALL_PRIMITIVE_TYPES[self.name] == 'c'
167    def is_integer_type(self):
168        return self.ALL_PRIMITIVE_TYPES[self.name] == 'i'
169    def is_float_type(self):
170        return self.ALL_PRIMITIVE_TYPES[self.name] == 'f'
171    def is_complex_type(self):
172        return self.ALL_PRIMITIVE_TYPES[self.name] == 'j'
173
174    def build_backend_type(self, ffi, finishlist):
175        return global_cache(self, ffi, 'new_primitive_type', self.name)
176
177
178class UnknownIntegerType(BasePrimitiveType):
179    _attrs_ = ('name',)
180
181    def __init__(self, name):
182        self.name = name
183        self.c_name_with_marker = name + '&'
184
185    def is_integer_type(self):
186        return True
187
188    def build_backend_type(self, ffi, finishlist):
189        raise NotImplementedError("integer type '%s' can only be used after "
190                                  "compilation" % self.name)
191
192class UnknownFloatType(BasePrimitiveType):
193    _attrs_ = ('name', )
194
195    def __init__(self, name):
196        self.name = name
197        self.c_name_with_marker = name + '&'
198
199    def build_backend_type(self, ffi, finishlist):
200        raise NotImplementedError("float type '%s' can only be used after "
201                                  "compilation" % self.name)
202
203
204class BaseFunctionType(BaseType):
205    _attrs_ = ('args', 'result', 'ellipsis', 'abi')
206
207    def __init__(self, args, result, ellipsis, abi=None):
208        self.args = args
209        self.result = result
210        self.ellipsis = ellipsis
211        self.abi = abi
212        #
213        reprargs = [arg._get_c_name() for arg in self.args]
214        if self.ellipsis:
215            reprargs.append('...')
216        reprargs = reprargs or ['void']
217        replace_with = self._base_pattern % (', '.join(reprargs),)
218        if abi is not None:
219            replace_with = replace_with[:1] + abi + ' ' + replace_with[1:]
220        self.c_name_with_marker = (
221            self.result.c_name_with_marker.replace('&', replace_with))
222
223
224class RawFunctionType(BaseFunctionType):
225    # Corresponds to a C type like 'int(int)', which is the C type of
226    # a function, but not a pointer-to-function.  The backend has no
227    # notion of such a type; it's used temporarily by parsing.
228    _base_pattern = '(&)(%s)'
229    is_raw_function = True
230
231    def build_backend_type(self, ffi, finishlist):
232        raise CDefError("cannot render the type %r: it is a function "
233                        "type, not a pointer-to-function type" % (self,))
234
235    def as_function_pointer(self):
236        return FunctionPtrType(self.args, self.result, self.ellipsis, self.abi)
237
238
239class FunctionPtrType(BaseFunctionType):
240    _base_pattern = '(*&)(%s)'
241
242    def build_backend_type(self, ffi, finishlist):
243        result = self.result.get_cached_btype(ffi, finishlist)
244        args = []
245        for tp in self.args:
246            args.append(tp.get_cached_btype(ffi, finishlist))
247        abi_args = ()
248        if self.abi == "__stdcall":
249            if not self.ellipsis:    # __stdcall ignored for variadic funcs
250                try:
251                    abi_args = (ffi._backend.FFI_STDCALL,)
252                except AttributeError:
253                    pass
254        return global_cache(self, ffi, 'new_function_type',
255                            tuple(args), result, self.ellipsis, *abi_args)
256
257    def as_raw_function(self):
258        return RawFunctionType(self.args, self.result, self.ellipsis, self.abi)
259
260
261class PointerType(BaseType):
262    _attrs_ = ('totype', 'quals')
263
264    def __init__(self, totype, quals=0):
265        self.totype = totype
266        self.quals = quals
267        extra = qualify(quals, " *&")
268        if totype.is_array_type:
269            extra = "(%s)" % (extra.lstrip(),)
270        self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra)
271
272    def build_backend_type(self, ffi, finishlist):
273        BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True)
274        return global_cache(self, ffi, 'new_pointer_type', BItem)
275
276voidp_type = PointerType(void_type)
277
278def ConstPointerType(totype):
279    return PointerType(totype, Q_CONST)
280
281const_voidp_type = ConstPointerType(void_type)
282
283
284class NamedPointerType(PointerType):
285    _attrs_ = ('totype', 'name')
286
287    def __init__(self, totype, name, quals=0):
288        PointerType.__init__(self, totype, quals)
289        self.name = name
290        self.c_name_with_marker = name + '&'
291
292
293class ArrayType(BaseType):
294    _attrs_ = ('item', 'length')
295    is_array_type = True
296
297    def __init__(self, item, length):
298        self.item = item
299        self.length = length
300        #
301        if length is None:
302            brackets = '&[]'
303        elif length == '...':
304            brackets = '&[/*...*/]'
305        else:
306            brackets = '&[%s]' % length
307        self.c_name_with_marker = (
308            self.item.c_name_with_marker.replace('&', brackets))
309
310    def length_is_unknown(self):
311        return isinstance(self.length, str)
312
313    def resolve_length(self, newlength):
314        return ArrayType(self.item, newlength)
315
316    def build_backend_type(self, ffi, finishlist):
317        if self.length_is_unknown():
318            raise CDefError("cannot render the type %r: unknown length" %
319                            (self,))
320        self.item.get_cached_btype(ffi, finishlist)   # force the item BType
321        BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist)
322        return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length)
323
324char_array_type = ArrayType(PrimitiveType('char'), None)
325
326
327class StructOrUnionOrEnum(BaseTypeByIdentity):
328    _attrs_ = ('name',)
329    forcename = None
330
331    def build_c_name_with_marker(self):
332        name = self.forcename or '%s %s' % (self.kind, self.name)
333        self.c_name_with_marker = name + '&'
334
335    def force_the_name(self, forcename):
336        self.forcename = forcename
337        self.build_c_name_with_marker()
338
339    def get_official_name(self):
340        assert self.c_name_with_marker.endswith('&')
341        return self.c_name_with_marker[:-1]
342
343
344class StructOrUnion(StructOrUnionOrEnum):
345    fixedlayout = None
346    completed = 0
347    partial = False
348    packed = 0
349
350    def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None):
351        self.name = name
352        self.fldnames = fldnames
353        self.fldtypes = fldtypes
354        self.fldbitsize = fldbitsize
355        self.fldquals = fldquals
356        self.build_c_name_with_marker()
357
358    def anonymous_struct_fields(self):
359        if self.fldtypes is not None:
360            for name, type in zip(self.fldnames, self.fldtypes):
361                if name == '' and isinstance(type, StructOrUnion):
362                    yield type
363
364    def enumfields(self, expand_anonymous_struct_union=True):
365        fldquals = self.fldquals
366        if fldquals is None:
367            fldquals = (0,) * len(self.fldnames)
368        for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes,
369                                              self.fldbitsize, fldquals):
370            if (name == '' and isinstance(type, StructOrUnion)
371                    and expand_anonymous_struct_union):
372                # nested anonymous struct/union
373                for result in type.enumfields():
374                    yield result
375            else:
376                yield (name, type, bitsize, quals)
377
378    def force_flatten(self):
379        # force the struct or union to have a declaration that lists
380        # directly all fields returned by enumfields(), flattening
381        # nested anonymous structs/unions.
382        names = []
383        types = []
384        bitsizes = []
385        fldquals = []
386        for name, type, bitsize, quals in self.enumfields():
387            names.append(name)
388            types.append(type)
389            bitsizes.append(bitsize)
390            fldquals.append(quals)
391        self.fldnames = tuple(names)
392        self.fldtypes = tuple(types)
393        self.fldbitsize = tuple(bitsizes)
394        self.fldquals = tuple(fldquals)
395
396    def get_cached_btype(self, ffi, finishlist, can_delay=False):
397        BType = StructOrUnionOrEnum.get_cached_btype(self, ffi, finishlist,
398                                                     can_delay)
399        if not can_delay:
400            self.finish_backend_type(ffi, finishlist)
401        return BType
402
403    def finish_backend_type(self, ffi, finishlist):
404        if self.completed:
405            if self.completed != 2:
406                raise NotImplementedError("recursive structure declaration "
407                                          "for '%s'" % (self.name,))
408            return
409        BType = ffi._cached_btypes[self]
410        #
411        self.completed = 1
412        #
413        if self.fldtypes is None:
414            pass    # not completing it: it's an opaque struct
415            #
416        elif self.fixedlayout is None:
417            fldtypes = [tp.get_cached_btype(ffi, finishlist)
418                        for tp in self.fldtypes]
419            lst = list(zip(self.fldnames, fldtypes, self.fldbitsize))
420            extra_flags = ()
421            if self.packed:
422                if self.packed == 1:
423                    extra_flags = (8,)    # SF_PACKED
424                else:
425                    extra_flags = (0, self.packed)
426            ffi._backend.complete_struct_or_union(BType, lst, self,
427                                                  -1, -1, *extra_flags)
428            #
429        else:
430            fldtypes = []
431            fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout
432            for i in range(len(self.fldnames)):
433                fsize = fieldsize[i]
434                ftype = self.fldtypes[i]
435                #
436                if isinstance(ftype, ArrayType) and ftype.length_is_unknown():
437                    # fix the length to match the total size
438                    BItemType = ftype.item.get_cached_btype(ffi, finishlist)
439                    nlen, nrest = divmod(fsize, ffi.sizeof(BItemType))
440                    if nrest != 0:
441                        self._verification_error(
442                            "field '%s.%s' has a bogus size?" % (
443                            self.name, self.fldnames[i] or '{}'))
444                    ftype = ftype.resolve_length(nlen)
445                    self.fldtypes = (self.fldtypes[:i] + (ftype,) +
446                                     self.fldtypes[i+1:])
447                #
448                BFieldType = ftype.get_cached_btype(ffi, finishlist)
449                if isinstance(ftype, ArrayType) and ftype.length is None:
450                    assert fsize == 0
451                else:
452                    bitemsize = ffi.sizeof(BFieldType)
453                    if bitemsize != fsize:
454                        self._verification_error(
455                            "field '%s.%s' is declared as %d bytes, but is "
456                            "really %d bytes" % (self.name,
457                                                 self.fldnames[i] or '{}',
458                                                 bitemsize, fsize))
459                fldtypes.append(BFieldType)
460            #
461            lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs))
462            ffi._backend.complete_struct_or_union(BType, lst, self,
463                                                  totalsize, totalalignment)
464        self.completed = 2
465
466    def _verification_error(self, msg):
467        raise VerificationError(msg)
468
469    def check_not_partial(self):
470        if self.partial and self.fixedlayout is None:
471            raise VerificationMissing(self._get_c_name())
472
473    def build_backend_type(self, ffi, finishlist):
474        self.check_not_partial()
475        finishlist.append(self)
476        #
477        return global_cache(self, ffi, 'new_%s_type' % self.kind,
478                            self.get_official_name(), key=self)
479
480
481class StructType(StructOrUnion):
482    kind = 'struct'
483
484
485class UnionType(StructOrUnion):
486    kind = 'union'
487
488
489class EnumType(StructOrUnionOrEnum):
490    kind = 'enum'
491    partial = False
492    partial_resolved = False
493
494    def __init__(self, name, enumerators, enumvalues, baseinttype=None):
495        self.name = name
496        self.enumerators = enumerators
497        self.enumvalues = enumvalues
498        self.baseinttype = baseinttype
499        self.build_c_name_with_marker()
500
501    def force_the_name(self, forcename):
502        StructOrUnionOrEnum.force_the_name(self, forcename)
503        if self.forcename is None:
504            name = self.get_official_name()
505            self.forcename = '$' + name.replace(' ', '_')
506
507    def check_not_partial(self):
508        if self.partial and not self.partial_resolved:
509            raise VerificationMissing(self._get_c_name())
510
511    def build_backend_type(self, ffi, finishlist):
512        self.check_not_partial()
513        base_btype = self.build_baseinttype(ffi, finishlist)
514        return global_cache(self, ffi, 'new_enum_type',
515                            self.get_official_name(),
516                            self.enumerators, self.enumvalues,
517                            base_btype, key=self)
518
519    def build_baseinttype(self, ffi, finishlist):
520        if self.baseinttype is not None:
521            return self.baseinttype.get_cached_btype(ffi, finishlist)
522        #
523        if self.enumvalues:
524            smallest_value = min(self.enumvalues)
525            largest_value = max(self.enumvalues)
526        else:
527            import warnings
528            try:
529                # XXX!  The goal is to ensure that the warnings.warn()
530                # will not suppress the warning.  We want to get it
531                # several times if we reach this point several times.
532                __warningregistry__.clear()
533            except NameError:
534                pass
535            warnings.warn("%r has no values explicitly defined; "
536                          "guessing that it is equivalent to 'unsigned int'"
537                          % self._get_c_name())
538            smallest_value = largest_value = 0
539        if smallest_value < 0:   # needs a signed type
540            sign = 1
541            candidate1 = PrimitiveType("int")
542            candidate2 = PrimitiveType("long")
543        else:
544            sign = 0
545            candidate1 = PrimitiveType("unsigned int")
546            candidate2 = PrimitiveType("unsigned long")
547        btype1 = candidate1.get_cached_btype(ffi, finishlist)
548        btype2 = candidate2.get_cached_btype(ffi, finishlist)
549        size1 = ffi.sizeof(btype1)
550        size2 = ffi.sizeof(btype2)
551        if (smallest_value >= ((-1) << (8*size1-1)) and
552            largest_value < (1 << (8*size1-sign))):
553            return btype1
554        if (smallest_value >= ((-1) << (8*size2-1)) and
555            largest_value < (1 << (8*size2-sign))):
556            return btype2
557        raise CDefError("%s values don't all fit into either 'long' "
558                        "or 'unsigned long'" % self._get_c_name())
559
560def unknown_type(name, structname=None):
561    if structname is None:
562        structname = '$%s' % name
563    tp = StructType(structname, None, None, None)
564    tp.force_the_name(name)
565    tp.origin = "unknown_type"
566    return tp
567
568def unknown_ptr_type(name, structname=None):
569    if structname is None:
570        structname = '$$%s' % name
571    tp = StructType(structname, None, None, None)
572    return NamedPointerType(tp, name)
573
574
575global_lock = allocate_lock()
576_typecache_cffi_backend = weakref.WeakValueDictionary()
577
578def get_typecache(backend):
579    # returns _typecache_cffi_backend if backend is the _cffi_backend
580    # module, or type(backend).__typecache if backend is an instance of
581    # CTypesBackend (or some FakeBackend class during tests)
582    if isinstance(backend, types.ModuleType):
583        return _typecache_cffi_backend
584    with global_lock:
585        if not hasattr(type(backend), '__typecache'):
586            type(backend).__typecache = weakref.WeakValueDictionary()
587        return type(backend).__typecache
588
589def global_cache(srctype, ffi, funcname, *args, **kwds):
590    key = kwds.pop('key', (funcname, args))
591    assert not kwds
592    try:
593        return ffi._typecache[key]
594    except KeyError:
595        pass
596    try:
597        res = getattr(ffi._backend, funcname)(*args)
598    except NotImplementedError as e:
599        raise NotImplementedError("%s: %r: %s" % (funcname, srctype, e))
600    # note that setdefault() on WeakValueDictionary is not atomic
601    # and contains a rare bug (http://bugs.python.org/issue19542);
602    # we have to use a lock and do it ourselves
603    cache = ffi._typecache
604    with global_lock:
605        res1 = cache.get(key)
606        if res1 is None:
607            cache[key] = res
608            return res
609        else:
610            return res1
611
612def pointer_cache(ffi, BType):
613    return global_cache('?', ffi, 'new_pointer_type', BType)
614
615def attach_exception_info(e, name):
616    if e.args and type(e.args[0]) is str:
617        e.args = ('%s: %s' % (name, e.args[0]),) + e.args[1:]
618