this repo has no description
1#!/usr/bin/env python3
2# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
3# $builtin-init-module$
4"""ctypes support module"""
5
6from builtins import _obj_as_int
7from weakref import ref
8
9from _builtins import (
10 _builtin,
11 _bytes_check,
12 _bytes_len,
13 _float_check,
14 _int_check,
15 _memoryview_check,
16 _memoryview_start,
17 _mmap_check,
18 _property,
19 _str_check,
20 _str_len,
21 _tuple_check,
22 _tuple_getitem,
23 _tuple_len,
24 _type,
25 _type_check,
26 _type_issubclass,
27 _Unbound,
28 _unimplemented,
29 _weakref_check,
30)
31
32
33_SIMPLE_TYPE_CHARS = "cbBhHiIlLdfuzZqQPXOv?g"
34
35FUNCFLAG_STDCALL = 0x0
36FUNCFLAG_CDECL = 0x1
37FUNCFLAG_HRESULT = 0x2
38FUNCFLAG_PYTHONAPI = 0x4
39FUNCFLAG_USE_ERRNO = 0x8
40FUNCFLAG_USE_LASTERROR = 0x10
41
42
43__version__ = "1.1.0"
44
45
46def _SimpleCData_value_to_type(value, typ, offset):
47 _builtin()
48
49
50# Metaclasses
51
52# Note: CPython does not define _CDataType as a metaclass, but rather as a
53# set of methods which are added to the other metaclasses.
54class _CDataType(type):
55 def from_address(cls, *args, **kwargs):
56 _unimplemented()
57
58 def from_buffer(cls, obj, offset=0):
59 if not hasattr(cls, "_type_"):
60 raise TypeError("abstract class")
61 if not _type_issubclass(cls, _SimpleCData) and not _type_issubclass(cls, Array):
62 _unimplemented()
63 if not _memoryview_check(obj) or not _mmap_check(obj.obj):
64 _unimplemented()
65 if offset < 0:
66 raise ValueError("offset cannot be negative")
67 if obj.readonly:
68 raise TypeError("underlying buffer is not writable")
69 if obj.strides != (1,):
70 raise TypeError("underlying buffer is not C contiguous")
71 obj_len = obj.shape[0]
72 type_size = sizeof(cls)
73 if type_size > obj_len - offset:
74 raise ValueError(
75 "Buffer size too small "
76 f"({obj_len} instead of at least {type_size + offset} bytes)"
77 )
78 result = cls.__new__(cls)
79 result._value = obj.obj
80 result._offset = offset + _memoryview_start(obj)
81 return result
82
83 def from_buffer_copy(cls, *args, **kwargs):
84 _unimplemented()
85
86 def from_param(cls, *args, **kwargs):
87 _unimplemented()
88
89 def in_dll(cls, *args, **kwargs):
90 _unimplemented()
91
92 def __mul__(cls, length):
93 if length < 0:
94 raise ValueError(f"Array length must be >= 0, not {length}")
95 if not isinstance(cls, type):
96 raise TypeError(f"Expected a type object")
97
98 key = (cls, length)
99 result = _array_from_ctype_cache.get(key)
100 if result is not None:
101 return result() if _weakref_check(result) else result
102
103 name = f"{cls.__name__}_Array_{length}"
104 result = PyCArrayType(name, (Array,), {"_length_": length, "_type_": cls})
105 _array_from_ctype_cache[key] = ref(
106 result, lambda _: _array_from_ctype_cache.pop(key)
107 )
108 return result
109
110
111def _CharArray_value_to_bytes(obj, le):
112 _builtin()
113
114
115def _CharArray_value_getter(self):
116 return _CharArray_value_to_bytes(self._value, self._length_)
117
118
119def _CharArray_value_setter(self, value):
120 if not _bytes_check(value):
121 raise TypeError(f"bytes expected instead of {_type(value).__name__} instance")
122 val_len = _bytes_len(value)
123 if val_len > self._length_:
124 raise ValueError("byte string too long")
125 if _mmap_check(self._value):
126 memoryview(self._value)[:val_len] = value
127 else:
128 self._value[:val_len] = value
129 if val_len < self._length_:
130 self._value[val_len] = 0
131
132
133class PyCArrayType(_CDataType):
134 def __new__(cls, name, bases, namespace):
135 result = super().__new__(cls, name, bases, namespace)
136 # The base class (Array), no checking necessary
137 if bases[0] == _CData:
138 return result
139
140 if not hasattr(result, "_length_"):
141 raise AttributeError("class must define a '_length_' attribute")
142 length = result._length_
143 if not _int_check(length):
144 raise TypeError("The '_length_' attribute must be an integer")
145 if length < 0:
146 raise ValueError("The '_length_' attribute must not be negative")
147
148 if not hasattr(result, "_type_"):
149 raise AttributeError("class must define a '_type_' attribute")
150 result_type = result._type_
151 if not _type_check(result_type) or not hasattr(result_type, "_type_"):
152 raise TypeError("_type_ must have storage info")
153 if result_type._type_ == "c":
154 result.value = _property(_CharArray_value_getter, _CharArray_value_setter)
155
156 return result
157
158
159class PyCFuncPtrType(_CDataType):
160 pass
161
162
163class PyCPointerType(_CDataType):
164 def from_param(cls, *args, **kwargs): # noqa: B902
165 _unimplemented()
166
167 def set_type(cls, *args, **kwargs): # noqa: B902
168 _unimplemented()
169
170
171class PyCSimpleType(_CDataType):
172 def __new__(cls, name, bases, namespace):
173 result = super().__new__(cls, name, bases, namespace)
174
175 # The base class (_SimpleCData), no checking necessary
176 if bases[0] == _CData:
177 return result
178
179 if not hasattr(result, "_type_"):
180 raise AttributeError("class must define a '_type_' attribute")
181
182 proto = result._type_
183 if not _str_check(proto):
184 raise TypeError("class must define a '_type_' string attribute")
185
186 if _str_len(proto) != 1:
187 raise ValueError(
188 "class must define a '_type_' attribute which must be a string \
189of length 1"
190 )
191
192 if proto not in _SIMPLE_TYPE_CHARS:
193 raise AttributeError(
194 "class must define a '_type_' attribute which must be\n"
195 + f"a single character string containing one of '{_SIMPLE_TYPE_CHARS}'."
196 )
197
198 # TODO(T61319024): CPython does more work here
199
200 return result
201
202 def from_param(cls, *args, **kwargs): # noqa: B902
203 _unimplemented()
204
205
206class PyCStructType(_CDataType):
207 pass
208
209
210class UnionType(_CDataType):
211 pass
212
213
214# End Metaclasses
215
216
217class _CData:
218 def __ctypes_from_outparam__(self):
219 _unimplemented()
220
221 def __reduce__(self):
222 _unimplemented()
223
224 def __setstate__(self):
225 _unimplemented()
226
227
228class _Pointer(_CData, metaclass=PyCPointerType):
229 def __new__(cls, name, bases, namespace):
230 _unimplemented()
231
232
233class _SimpleCData(_CData, metaclass=PyCSimpleType):
234 def __init__(self, value=_Unbound):
235 self._value_setter(value)
236 self._offset = 0
237
238 def __new__(cls, *args, **kwargs):
239 if not hasattr(cls, "_type_"):
240 raise TypeError("abstract class")
241 return super().__new__(cls, args, kwargs)
242
243 def _value_getter(self):
244 return _SimpleCData_value_to_type(self._value, self._type_, self._offset)
245
246 def _value_setter(self, value):
247 if self._type_ == "H" or self._type_ == "L":
248 if _float_check(value):
249 raise TypeError("int expected instead of float")
250 if value is _Unbound:
251 self._value = 0
252 else:
253 self._value = _obj_as_int(value)
254 else:
255 _unimplemented()
256
257 value = _property(_value_getter, _value_setter)
258
259
260class ArgumentError(Exception):
261 pass
262
263
264class Array(_CData, metaclass=PyCArrayType):
265 def __init__(self, *args, **kwargs):
266 self._offset = 0
267 if _tuple_len(args) > 1:
268 _unimplemented()
269 elif _tuple_len(args) == 1:
270 if self._type_._type_ != "c":
271 _unimplemented()
272 arg0 = _tuple_getitem(args, 0)
273 if not _bytes_check(arg0) or _bytes_len(arg0) != 1:
274 _unimplemented()
275 self.value = arg0
276
277 def __len__(self):
278 _unimplemented()
279
280 def __getitem__(self):
281 _unimplemented()
282
283 def __new__(cls, *args, **kwargs):
284 if not hasattr(cls, "_type_"):
285 raise TypeError("abstract class")
286 result = super().__new__(cls, args, kwargs)
287 result._value = bytearray(bytes(cls._length_))
288 return result
289
290 def __setitem__(self):
291 _unimplemented()
292
293 def __delitem__(self):
294 _unimplemented()
295
296
297# TODO(T66886328): Delete the memset patch once __call__ is complete
298def _memset(addr, val, size):
299 _builtin()
300
301
302class CFuncPtr(_CData, metaclass=PyCFuncPtrType):
303 def __call__(self, *args, **kwargs):
304 # TODO(T66886328): Delete the memset patch once __call__ is complete
305 if self.fn_pointer == _memset_addr: # noqa: F821
306 if _tuple_len(args) != 3:
307 raise TypeError("this function takes at least 3 arguments")
308 addr = _tuple_getitem(args, 0)
309 val = _tuple_getitem(args, 1)
310 size = _tuple_getitem(args, 2)
311 if not _int_check(addr) or not _int_check(val) or not _int_check(size):
312 _unimplemented()
313 return _memset(addr, val, size)
314 if _tuple_len(args) != 0:
315 _unimplemented()
316 if hasattr(self, "callable") and self.callable is not None:
317 _unimplemented()
318 return _call_cfuncptr(self.fn_pointer, self.restype._type_)
319
320 def __new__(cls, *args, **kwargs):
321 if _tuple_len(args) != 1:
322 _unimplemented()
323
324 arg = args[0]
325 if _int_check(arg):
326 result = super().__new__(cls)
327 result.fn_pointer = arg
328 elif callable(arg):
329 result = super().__new__(cls)
330 result.callable = arg
331 elif _tuple_check(arg):
332 name = arg[0]
333 if _bytes_check(name):
334 _unimplemented()
335 if not _str_check(name):
336 # Note: integer argument only supported on WIN32
337 raise TypeError("function name must be string, bytes object or integer")
338 dll = arg[1]
339 handle = dll._handle
340 if not _int_check(handle):
341 raise TypeError(
342 "the _handle attribute of the second argument must be an integer"
343 )
344 result = super().__new__(cls)
345 result.fn_pointer = _shared_object_symbol_address(handle, name)
346 else:
347 raise TypeError("argument must be callable or integer function address")
348
349 result.restype = cls._restype_
350 return result
351
352
353class Structure(_CData, metaclass=PyCStructType):
354 def __init__(self, *args, **kwargs):
355 _unimplemented()
356
357 def __new__(cls, *args, **kwargs):
358 _unimplemented()
359
360
361class Union(_CData, metaclass=UnionType):
362 def __init__(self, *args, **kwargs):
363 _unimplemented()
364
365 def __new__(cls, *args, **kwargs):
366 _unimplemented()
367
368
369def _addressof(obj):
370 _builtin()
371
372
373_array_from_ctype_cache = {}
374
375
376_pointer_type_cache = {}
377
378
379def _call_cfuncptr(function_ptr, result_type):
380 _builtin()
381
382
383def _sizeof_typeclass(cdata_type):
384 _builtin()
385
386
387def _shared_object_symbol_address(handle, name):
388 _builtin()
389
390
391def addressof(obj):
392 if not issubclass(type(obj), _CData):
393 raise TypeError("invalid type")
394 # TODO(T67017526): Remove mmap patch
395 # addressof can only be called on objects which are not allocated on Pyro's
396 # internal heap (such as an mmap). Objects on Pyro's heap do not have stable
397 # addresses due to the relocating garbage collector, so we should not make them
398 # visible to the user
399 if (
400 not _type_issubclass(_type(obj), _SimpleCData)
401 and not _type_issubclass(_type(obj), Array)
402 ) or not _mmap_check(obj._value):
403 _unimplemented()
404 return _addressof(obj._value)
405
406
407def alignment(obj_or_type):
408 _unimplemented()
409
410
411def byref(obj, offset=0):
412 _unimplemented()
413
414
415def dlopen(name, flag):
416 _builtin()
417
418
419def get_errno():
420 _unimplemented()
421
422
423def POINTER(cls):
424 result = _pointer_type_cache.get(cls)
425 if result is not None:
426 return result
427
428 if isinstance(cls, str):
429 result = PyCPointerType("LP_" + cls, (_Pointer,), {})
430 key = id(result)
431 elif isinstance(cls, type):
432 result = PyCPointerType("LP_" + cls.__name__, (_Pointer,), {"_type_": cls})
433 key = cls
434 else:
435 raise TypeError("must be a ctypes type")
436
437 _pointer_type_cache[key] = result
438 return result
439
440
441def pointer(obj):
442 _unimplemented()
443
444
445def resize(obj, size):
446 _unimplemented()
447
448
449def set_errno(value):
450 _unimplemented()
451
452
453def sizeof(obj_or_type):
454 if not _type_check(obj_or_type):
455 ty = _type(obj_or_type)
456 else:
457 ty = obj_or_type
458 if not _type_issubclass(ty, _CData):
459 raise TypeError("this type has no size")
460 if _type_issubclass(ty, Array):
461 return ty._length_ * sizeof(ty._type_)
462 if _type_issubclass(ty, _SimpleCData):
463 return _sizeof_typeclass(ty._type_)
464 _unimplemented()