Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
model.py619 linesDownload Raw Back to cffi
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 = " *&"
268        if totype.is_array_type:
269            extra = "(%s)" % (extra.lstrip(),)
270        extra = qualify(quals, extra)
271        self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra)
272
273    def build_backend_type(self, ffi, finishlist):
274        BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True)
275        return global_cache(self, ffi, 'new_pointer_type', BItem)
276
277voidp_type = PointerType(void_type)
278
279def ConstPointerType(totype):
280    return PointerType(totype, Q_CONST)
281
282const_voidp_type = ConstPointerType(void_type)
283
284
285class NamedPointerType(PointerType):
286    _attrs_ = ('totype', 'name')
287
288    def __init__(self, totype, name, quals=0):
289        PointerType.__init__(self, totype, quals)
290        self.name = name
291        self.c_name_with_marker = name + '&'
292
293
294class ArrayType(BaseType):
295    _attrs_ = ('item', 'length')
296    is_array_type = True
297
298    def __init__(self, item, length):
299        self.item = item
300        self.length = length
301        #
302        if length is None:
303            brackets = '&[]'
304        elif length == '...':
305            brackets = '&[/*...*/]'
306        else:
307            brackets = '&[%s]' % length
308        self.c_name_with_marker = (
309            self.item.c_name_with_marker.replace('&', brackets))
310
311    def length_is_unknown(self):
312        return isinstance(self.length, str)
313
314    def resolve_length(self, newlength):
315        return ArrayType(self.item, newlength)
316
317    def build_backend_type(self, ffi, finishlist):
318        if self.length_is_unknown():
319            raise CDefError("cannot render the type %r: unknown length" %
320                            (self,))
321        self.item.get_cached_btype(ffi, finishlist)   # force the item BType
322        BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist)
323        return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length)
324
325char_array_type = ArrayType(PrimitiveType('char'), None)
326
327
328class StructOrUnionOrEnum(BaseTypeByIdentity):
329    _attrs_ = ('name',)
330    forcename = None
331
332    def build_c_name_with_marker(self):
333        name = self.forcename or '%s %s' % (self.kind, self.name)
334        self.c_name_with_marker = name + '&'
335
336    def force_the_name(self, forcename):
337        self.forcename = forcename
338        self.build_c_name_with_marker()
339
340    def get_official_name(self):
341        assert self.c_name_with_marker.endswith('&')
342        return self.c_name_with_marker[:-1]
343
344
345class StructOrUnion(StructOrUnionOrEnum):
346    fixedlayout = None
347    completed = 0
348    partial = False
349    packed = 0
350
351    def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None):
352        self.name = name
353        self.fldnames = fldnames
354        self.fldtypes = fldtypes
355        self.fldbitsize = fldbitsize
356        self.fldquals = fldquals
357        self.build_c_name_with_marker()
358
359    def anonymous_struct_fields(self):
360        if self.fldtypes is not None:
361            for name, type in zip(self.fldnames, self.fldtypes):
362                if name == '' and isinstance(type, StructOrUnion):
363                    yield type
364
365    def enumfields(self, expand_anonymous_struct_union=True):
366        fldquals = self.fldquals
367        if fldquals is None:
368            fldquals = (0,) * len(self.fldnames)
369        for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes,
370                                              self.fldbitsize, fldquals):
371            if (name == '' and isinstance(type, StructOrUnion)
372                    and expand_anonymous_struct_union):
373                # nested anonymous struct/union
374                for result in type.enumfields():
375                    yield result
376            else:
377                yield (name, type, bitsize, quals)
378
379    def force_flatten(self):
380        # force the struct or union to have a declaration that lists
381        # directly all fields returned by enumfields(), flattening
382        # nested anonymous structs/unions.
383        names = []
384        types = []
385        bitsizes = []
386        fldquals = []
387        for name, type, bitsize, quals in self.enumfields():
388            names.append(name)
389            types.append(type)
390            bitsizes.append(bitsize)
391            fldquals.append(quals)
392        self.fldnames = tuple(names)
393        self.fldtypes = tuple(types)
394        self.fldbitsize = tuple(bitsizes)
395        self.fldquals = tuple(fldquals)
396
397    def get_cached_btype(self, ffi, finishlist, can_delay=False):
398        BType = StructOrUnionOrEnum.get_cached_btype(self, ffi, finishlist,
399                                                     can_delay)
400        if not can_delay:
401            self.finish_backend_type(ffi, finishlist)
402        return BType
403
404    def finish_backend_type(self, ffi, finishlist):
405        if self.completed:
406            if self.completed != 2:
407                raise NotImplementedError("recursive structure declaration "
408                                          "for '%s'" % (self.name,))
409            return
410        BType = ffi._cached_btypes[self]
411        #
412        self.completed = 1
413        #
414        if self.fldtypes is None:
415            pass    # not completing it: it's an opaque struct
416            #
417        elif self.fixedlayout is None:
418            fldtypes = [tp.get_cached_btype(ffi, finishlist)
419                        for tp in self.fldtypes]
420            lst = list(zip(self.fldnames, fldtypes, self.fldbitsize))
421            extra_flags = ()
422            if self.packed:
423                if self.packed == 1:
424                    extra_flags = (8,)    # SF_PACKED
425                else:
426                    extra_flags = (0, self.packed)
427            ffi._backend.complete_struct_or_union(BType, lst, self,
428                                                  -1, -1, *extra_flags)
429            #
430        else:
431            fldtypes = []
432            fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout
433            for i in range(len(self.fldnames)):
434                fsize = fieldsize[i]
435                ftype = self.fldtypes[i]
436                #
437                if isinstance(ftype, ArrayType) and ftype.length_is_unknown():
438                    # fix the length to match the total size
439                    BItemType = ftype.item.get_cached_btype(ffi, finishlist)
440                    nlen, nrest = divmod(fsize, ffi.sizeof(BItemType))
441                    if nrest != 0:
442                        self._verification_error(
443                            "field '%s.%s' has a bogus size?" % (
444                            self.name, self.fldnames[i] or '{}'))
445                    ftype = ftype.resolve_length(nlen)
446                    self.fldtypes = (self.fldtypes[:i] + (ftype,) +
447                                     self.fldtypes[i+1:])
448                #
449                BFieldType = ftype.get_cached_btype(ffi, finishlist)
450                if isinstance(ftype, ArrayType) and ftype.length is None:
451                    assert fsize == 0
452                else:
453                    bitemsize = ffi.sizeof(BFieldType)
454                    if bitemsize != fsize:
455                        self._verification_error(
456                            "field '%s.%s' is declared as %d bytes, but is "
457                            "really %d bytes" % (self.name,
458                                                 self.fldnames[i] or '{}',
459                                                 bitemsize, fsize))
460                fldtypes.append(BFieldType)
461            #
462            lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs))
463            ffi._backend.complete_struct_or_union(BType, lst, self,
464                                                  totalsize, totalalignment)
465        self.completed = 2
466
467    def _verification_error(self, msg):
468        raise VerificationError(msg)
469
470    def check_not_partial(self):
471        if self.partial and self.fixedlayout is None:
472            raise VerificationMissing(self._get_c_name())
473
474    def build_backend_type(self, ffi, finishlist):
475        self.check_not_partial()
476        finishlist.append(self)
477        #
478        return global_cache(self, ffi, 'new_%s_type' % self.kind,
479                            self.get_official_name(), key=self)
480
481
482class StructType(StructOrUnion):
483    kind = 'struct'
484
485
486class UnionType(StructOrUnion):
487    kind = 'union'
488
489
490class EnumType(StructOrUnionOrEnum):
491    kind = 'enum'
492    partial = False
493    partial_resolved = False
494
495    def __init__(self, name, enumerators, enumvalues, baseinttype=None):
496        self.name = name
497        self.enumerators = enumerators
498        self.enumvalues = enumvalues
499        self.baseinttype = baseinttype
500        self.build_c_name_with_marker()
501
502    def force_the_name(self, forcename):
503        StructOrUnionOrEnum.force_the_name(self, forcename)
504        if self.forcename is None:
505            name = self.get_official_name()
506            self.forcename = '$' + name.replace(' ', '_')
507
508    def check_not_partial(self):
509        if self.partial and not self.partial_resolved:
510            raise VerificationMissing(self._get_c_name())
511
512    def build_backend_type(self, ffi, finishlist):
513        self.check_not_partial()
514        base_btype = self.build_baseinttype(ffi, finishlist)
515        return global_cache(self, ffi, 'new_enum_type',
516                            self.get_official_name(),
517                            self.enumerators, self.enumvalues,
518                            base_btype, key=self)
519
520    def build_baseinttype(self, ffi, finishlist):
521        if self.baseinttype is not None:
522            return self.baseinttype.get_cached_btype(ffi, finishlist)
523        #
524        if self.enumvalues:
525            smallest_value = min(self.enumvalues)
526            largest_value = max(self.enumvalues)
527        else:
528            import warnings
529            try:
530                # XXX!  The goal is to ensure that the warnings.warn()
531                # will not suppress the warning.  We want to get it
532                # several times if we reach this point several times.
533                __warningregistry__.clear()
534            except NameError:
535                pass
536            warnings.warn("%r has no values explicitly defined; "
537                          "guessing that it is equivalent to 'unsigned int'"
538                          % self._get_c_name())
539            smallest_value = largest_value = 0
540        if smallest_value < 0:   # needs a signed type
541            sign = 1
542            candidate1 = PrimitiveType("int")
543            candidate2 = PrimitiveType("long")
544        else:
545            sign = 0
546            candidate1 = PrimitiveType("unsigned int")
547            candidate2 = PrimitiveType("unsigned long")
548        btype1 = candidate1.get_cached_btype(ffi, finishlist)
549        btype2 = candidate2.get_cached_btype(ffi, finishlist)
550        size1 = ffi.sizeof(btype1)
551        size2 = ffi.sizeof(btype2)
552        if (smallest_value >= ((-1) << (8*size1-1)) and
553            largest_value < (1 << (8*size1-sign))):
554            return btype1
555        if (smallest_value >= ((-1) << (8*size2-1)) and
556            largest_value < (1 << (8*size2-sign))):
557            return btype2
558        raise CDefError("%s values don't all fit into either 'long' "
559                        "or 'unsigned long'" % self._get_c_name())
560
561def unknown_type(name, structname=None):
562    if structname is None:
563        structname = '$%s' % name
564    tp = StructType(structname, None, None, None)
565    tp.force_the_name(name)
566    tp.origin = "unknown_type"
567    return tp
568
569def unknown_ptr_type(name, structname=None):
570    if structname is None:
571        structname = '$$%s' % name
572    tp = StructType(structname, None, None, None)
573    return NamedPointerType(tp, name)
574
575
576global_lock = allocate_lock()
577_typecache_cffi_backend = weakref.WeakValueDictionary()
578
579def get_typecache(backend):
580    # returns _typecache_cffi_backend if backend is the _cffi_backend
581    # module, or type(backend).__typecache if backend is an instance of
582    # CTypesBackend (or some FakeBackend class during tests)
583    if isinstance(backend, types.ModuleType):
584        return _typecache_cffi_backend
585    with global_lock:
586        if not hasattr(type(backend), '__typecache'):
587            type(backend).__typecache = weakref.WeakValueDictionary()
588        return type(backend).__typecache
589
590def global_cache(srctype, ffi, funcname, *args, **kwds):
591    key = kwds.pop('key', (funcname, args))
592    assert not kwds
593    try:
594        return ffi._typecache[key]
595    except KeyError:
596        pass
597    try:
598        res = getattr(ffi._backend, funcname)(*args)
599    except NotImplementedError as e:
600        raise NotImplementedError("%s: %r: %s" % (funcname, srctype, e))
601    # note that setdefault() on WeakValueDictionary is not atomic
602    # and contains a rare bug (http://bugs.python.org/issue19542);
603    # we have to use a lock and do it ourselves
604    cache = ffi._typecache
605    with global_lock:
606        res1 = cache.get(key)
607        if res1 is None:
608            cache[key] = res
609            return res
610        else:
611            return res1
612
613def pointer_cache(ffi, BType):
614    return global_cache('?', ffi, 'new_pointer_type', BType)
615
616def attach_exception_info(e, name):
617    if e.args and type(e.args[0]) is str:
618        e.args = ('%s: %s' % (name, e.args[0]),) + e.args[1:]
619 
codekingpro/portable-devtools · Team Ai