codekingpro/portable-devtools
114k
1import types2import weakref3 4from .lock import allocate_lock5from .error import CDefError, VerificationError, VerificationMissing6 7# type qualifiers8Q_CONST = 0x019Q_RESTRICT = 0x0210Q_VOLATILE = 0x0411 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 in20 # _cffi_include.h for it (and in its copies, documented there)21 replace_with = ' __restrict ' + replace_with.lstrip()22 return replace_with23 24 25class BaseTypeByIdentity(object):26 is_array_type = False27 is_raw_function = False28 29 def get_c_name(self, replace_with='', context='a C file', quals=0):30 result = self.c_name_with_marker31 assert result.count('&') == 132 # 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_with37 elif not replace_with[0] in '[(':38 replace_with = ' ' + replace_with39 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 result46 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 False55 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 BType63 return BType64 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__ and76 self._get_items() == other._get_items())77 78 def __ne__(self, other):79 return not self == other80 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 False100 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 '_cffi_float_complex_t': 'j',121 '_cffi_double_complex_t': 'j',122 '_Bool': 'i',123 # the following types are not primitive in the C sense124 '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_TYPES162 self.name = name163 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 = name183 self.c_name_with_marker = name + '&'184 185 def is_integer_type(self):186 return True187 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 = name197 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 = args209 self.result = result210 self.ellipsis = ellipsis211 self.abi = abi212 #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 of226 # a function, but not a pointer-to-function. The backend has no227 # notion of such a type; it's used temporarily by parsing.228 _base_pattern = '(&)(%s)'229 is_raw_function = True230 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 funcs250 try:251 abi_args = (ffi._backend.FFI_STDCALL,)252 except AttributeError:253 pass254 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 = totype266 self.quals = quals267 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 = name291 self.c_name_with_marker = name + '&'292 293 294class ArrayType(BaseType):295 _attrs_ = ('item', 'length')296 is_array_type = True297 298 def __init__(self, item, length):299 self.item = item300 self.length = length301 #302 if length is None:303 brackets = '&[]'304 elif length == '...':305 brackets = '&[/*...*/]'306 else:307 brackets = '&[%s]' % length308 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 BType322 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 = None331 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 = forcename338 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 = None347 completed = 0348 partial = False349 packed = 0350 351 def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None):352 self.name = name353 self.fldnames = fldnames354 self.fldtypes = fldtypes355 self.fldbitsize = fldbitsize356 self.fldquals = fldquals357 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 type364 365 def enumfields(self, expand_anonymous_struct_union=True):366 fldquals = self.fldquals367 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/union374 for result in type.enumfields():375 yield result376 else:377 yield (name, type, bitsize, quals)378 379 def force_flatten(self):380 # force the struct or union to have a declaration that lists381 # directly all fields returned by enumfields(), flattening382 # 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 BType403 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 return410 BType = ffi._cached_btypes[self]411 #412 self.completed = 1413 #414 if self.fldtypes is None:415 pass # not completing it: it's an opaque struct416 #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_PACKED425 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.fixedlayout433 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 size439 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 == 0452 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 = 2466 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 = False493 partial_resolved = False494 495 def __init__(self, name, enumerators, enumvalues, baseinttype=None):496 self.name = name497 self.enumerators = enumerators498 self.enumvalues = enumvalues499 self.baseinttype = baseinttype500 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 warnings529 try:530 # XXX! The goal is to ensure that the warnings.warn()531 # will not suppress the warning. We want to get it532 # several times if we reach this point several times.533 __warningregistry__.clear()534 except NameError:535 pass536 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 = 0540 if smallest_value < 0: # needs a signed type541 sign = 1542 candidate1 = PrimitiveType("int")543 candidate2 = PrimitiveType("long")544 else:545 sign = 0546 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)) and553 largest_value < (1 << (8*size1-sign))):554 return btype1555 if (smallest_value >= ((-1) << (8*size2-1)) and556 largest_value < (1 << (8*size2-sign))):557 return btype2558 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' % name564 tp = StructType(structname, None, None, None)565 tp.force_the_name(name)566 tp.origin = "unknown_type"567 return tp568 569def unknown_ptr_type(name, structname=None):570 if structname is None:571 structname = '$$%s' % name572 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_backend581 # module, or type(backend).__typecache if backend is an instance of582 # CTypesBackend (or some FakeBackend class during tests)583 if isinstance(backend, types.ModuleType):584 return _typecache_cffi_backend585 with global_lock:586 if not hasattr(type(backend), '__typecache'):587 type(backend).__typecache = weakref.WeakValueDictionary()588 return type(backend).__typecache589 590def global_cache(srctype, ffi, funcname, *args, **kwds):591 key = kwds.pop('key', (funcname, args))592 assert not kwds593 try:594 return ffi._typecache[key]595 except KeyError:596 pass597 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 atomic602 # and contains a rare bug (http://bugs.python.org/issue19542);603 # we have to use a lock and do it ourselves604 cache = ffi._typecache605 with global_lock:606 res1 = cache.get(key)607 if res1 is None:608 cache[key] = res609 return res610 else:611 return res1612 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 