frida 17.15.5 → 17.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Makefile +1 -1
- package/build/src/frida.d.ts +13 -2
- package/build/src/frida.js +4 -1
- package/build/src/frida_binding.d.ts +21 -1
- package/frida-bindgen/frida_bindgen_core/__init__.py +8 -0
- package/frida-bindgen/frida_bindgen_core/loader.py +57 -0
- package/frida-bindgen/frida_bindgen_core/model.py +853 -0
- package/frida-bindgen/frida_bindgen_core/naming.py +40 -0
- package/package.json +2 -1
- package/releng/deps.toml +1 -1
- package/releng/env_generic.py +12 -2
- package/releng/machine_spec.py +8 -5
- package/src/frida_bindgen/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/__main__.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/cli.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/codegen.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/customization.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/loader.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/model.cpython-312.pyc +0 -0
- package/src/frida_bindgen/loader.py +12 -27
- package/src/frida_bindgen/model.py +97 -830
- package/src/meson.build +8 -2
- package/subprojects/frida-core.wrap +1 -1
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from typing import (Callable, Iterator, List, Optional, Sequence, Tuple)
|
|
9
|
+
|
|
10
|
+
from .naming import to_snake_case
|
|
11
|
+
|
|
12
|
+
CORE_NAMESPACE = "http://www.gtk.org/introspection/core/1.0"
|
|
13
|
+
C_NAMESPACE = "http://www.gtk.org/introspection/c/1.0"
|
|
14
|
+
GLIB_NAMESPACE = "http://www.gtk.org/introspection/glib/1.0"
|
|
15
|
+
GIR_NAMESPACES = {"": CORE_NAMESPACE, "glib": GLIB_NAMESPACE}
|
|
16
|
+
|
|
17
|
+
CORE_TAG_PREFIX = f"{{{CORE_NAMESPACE}}}"
|
|
18
|
+
|
|
19
|
+
NUMERIC_GIR_TYPES = {
|
|
20
|
+
"gsize",
|
|
21
|
+
"gssize",
|
|
22
|
+
"gint",
|
|
23
|
+
"guint",
|
|
24
|
+
"glong",
|
|
25
|
+
"gulong",
|
|
26
|
+
"gint8",
|
|
27
|
+
"gint16",
|
|
28
|
+
"gint32",
|
|
29
|
+
"gint64",
|
|
30
|
+
"guint8",
|
|
31
|
+
"guint16",
|
|
32
|
+
"guint32",
|
|
33
|
+
"guint64",
|
|
34
|
+
"gfloat",
|
|
35
|
+
"gdouble",
|
|
36
|
+
"GType",
|
|
37
|
+
"GQuark",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
PRIMITIVE_GIR_TYPES = NUMERIC_GIR_TYPES | {
|
|
41
|
+
"gpointer",
|
|
42
|
+
"gboolean",
|
|
43
|
+
"gchar",
|
|
44
|
+
"utf8",
|
|
45
|
+
"utf8[]",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ResolveTypeCallback = Callable[[str], Tuple[str, ET.Element]]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Factory:
|
|
53
|
+
class_object_type: Callable
|
|
54
|
+
interface_object_type: Callable
|
|
55
|
+
constructor: Callable
|
|
56
|
+
method: Callable
|
|
57
|
+
parameter: Callable
|
|
58
|
+
return_value: Callable
|
|
59
|
+
signal: Callable
|
|
60
|
+
property_: Callable
|
|
61
|
+
enumeration: Callable
|
|
62
|
+
enumeration_member: Callable
|
|
63
|
+
model: Callable
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Model:
|
|
68
|
+
namespace: Namespace
|
|
69
|
+
_object_types: OrderedDict
|
|
70
|
+
enumerations: OrderedDict
|
|
71
|
+
customizations: Optional[object] = None
|
|
72
|
+
error_domain: Optional[object] = None
|
|
73
|
+
factory: Optional[Factory] = None
|
|
74
|
+
|
|
75
|
+
@cached_property
|
|
76
|
+
def object_types(self) -> OrderedDict:
|
|
77
|
+
result = OrderedDict()
|
|
78
|
+
if self.customizations is not None:
|
|
79
|
+
type_customizations = self.customizations.type_customizations
|
|
80
|
+
else:
|
|
81
|
+
type_customizations = {}
|
|
82
|
+
for k, v in self._object_types.items():
|
|
83
|
+
custom = type_customizations.get(k)
|
|
84
|
+
if custom is None or not custom.drop:
|
|
85
|
+
result[k] = v
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
def resolve_object_type(self, name: str) -> ObjectType:
|
|
89
|
+
bare_name = name.split(".", maxsplit=1)[-1]
|
|
90
|
+
return self.object_types[bare_name]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class Namespace:
|
|
95
|
+
name: str
|
|
96
|
+
identifier_prefixes: str
|
|
97
|
+
element: ET.Element
|
|
98
|
+
|
|
99
|
+
@cached_property
|
|
100
|
+
def type_elements(self):
|
|
101
|
+
result = {}
|
|
102
|
+
for toplevel in self.element.findall("./*[@name]", GIR_NAMESPACES):
|
|
103
|
+
name = toplevel.get("name")
|
|
104
|
+
result[name] = toplevel
|
|
105
|
+
for callback in toplevel.findall("./callback", GIR_NAMESPACES):
|
|
106
|
+
result[name + callback.get("name")] = callback
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class ObjectType:
|
|
112
|
+
name: str
|
|
113
|
+
c_type: str
|
|
114
|
+
get_type: str
|
|
115
|
+
type_struct: str
|
|
116
|
+
_parent: Optional[str]
|
|
117
|
+
_constructors: List[ET.Element]
|
|
118
|
+
_methods: List[ET.Element]
|
|
119
|
+
_properties: List[ET.Element]
|
|
120
|
+
_signals: List[ET.Element]
|
|
121
|
+
resolve_type: ResolveTypeCallback
|
|
122
|
+
|
|
123
|
+
model: Optional[Model]
|
|
124
|
+
|
|
125
|
+
@cached_property
|
|
126
|
+
def parent(self) -> ObjectType:
|
|
127
|
+
if self._parent is None:
|
|
128
|
+
return None
|
|
129
|
+
return self.model.resolve_object_type(self._parent)
|
|
130
|
+
|
|
131
|
+
@cached_property
|
|
132
|
+
def is_frida_options(self) -> bool:
|
|
133
|
+
return self.c_type.startswith("Frida") and self.c_type.endswith("Options")
|
|
134
|
+
|
|
135
|
+
@cached_property
|
|
136
|
+
def is_frida_list(self) -> bool:
|
|
137
|
+
return self.c_type.startswith("Frida") and self.c_type.endswith("List")
|
|
138
|
+
|
|
139
|
+
@cached_property
|
|
140
|
+
def customizations(self):
|
|
141
|
+
return self.model.customizations.type_customizations.get(self.name)
|
|
142
|
+
|
|
143
|
+
@cached_property
|
|
144
|
+
def constructors(self) -> List[Constructor]:
|
|
145
|
+
factory = self.model.factory
|
|
146
|
+
constructors = []
|
|
147
|
+
custom = self.customizations
|
|
148
|
+
for element in self._constructors:
|
|
149
|
+
if element.get("introspectable") == "0" or element.get("deprecated") == "1":
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
name = element.get("name")
|
|
153
|
+
|
|
154
|
+
if custom is not None:
|
|
155
|
+
ccust = custom.constructor
|
|
156
|
+
if ccust is not None and ccust.drop:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
(
|
|
160
|
+
c_identifier,
|
|
161
|
+
finish_c_identifier,
|
|
162
|
+
param_list,
|
|
163
|
+
has_closure_param,
|
|
164
|
+
throws,
|
|
165
|
+
result_element,
|
|
166
|
+
) = extract_callable_details(element, element, self, self.resolve_type)
|
|
167
|
+
if has_closure_param or finish_c_identifier is not None:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
constructors.append(
|
|
171
|
+
factory.constructor(
|
|
172
|
+
name, c_identifier, finish_c_identifier, param_list, throws, self
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
return constructors
|
|
176
|
+
|
|
177
|
+
@cached_property
|
|
178
|
+
def methods(self) -> List[Method]:
|
|
179
|
+
factory = self.model.factory
|
|
180
|
+
methods = []
|
|
181
|
+
c_prop_names = {prop.c_name for prop in self.properties}
|
|
182
|
+
custom = self.customizations
|
|
183
|
+
for element in self._methods:
|
|
184
|
+
name = element.get("name")
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
element.get("introspectable") == "0"
|
|
188
|
+
or name.startswith("_")
|
|
189
|
+
or name.endswith("_sync")
|
|
190
|
+
or name.endswith("_finish")
|
|
191
|
+
):
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
if custom is not None:
|
|
195
|
+
mcust = custom.methods.get(name, None)
|
|
196
|
+
if mcust is not None and mcust.drop:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
finish_func = element.get(f"{{{GLIB_NAMESPACE}}}finish-func")
|
|
200
|
+
if finish_func is None:
|
|
201
|
+
finish_func = f"{name}_finish"
|
|
202
|
+
result_element = next(
|
|
203
|
+
(m for m in self._methods if m.get("name") == finish_func), element
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
(
|
|
207
|
+
c_identifier,
|
|
208
|
+
finish_c_identifier,
|
|
209
|
+
param_list,
|
|
210
|
+
has_closure_param,
|
|
211
|
+
throws,
|
|
212
|
+
result_element,
|
|
213
|
+
) = extract_callable_details(
|
|
214
|
+
element, result_element, self, self.resolve_type
|
|
215
|
+
)
|
|
216
|
+
if has_closure_param:
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
retval_element = result_element.find(".//return-value", GIR_NAMESPACES)
|
|
220
|
+
rettype = extract_type_from_entity(retval_element, self.resolve_type)
|
|
221
|
+
if rettype is not None:
|
|
222
|
+
if rettype.is_frida_options:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
nullable = retval_element.get("nullable") == "1"
|
|
226
|
+
|
|
227
|
+
ownership_val = retval_element.get("transfer-ownership")
|
|
228
|
+
transfer_ownership = (
|
|
229
|
+
TransferOwnership[ownership_val]
|
|
230
|
+
if ownership_val is not None
|
|
231
|
+
else TransferOwnership.none
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
retval = factory.return_value(
|
|
235
|
+
rettype, nullable, transfer_ownership, self
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
retval = None
|
|
239
|
+
|
|
240
|
+
if element.get(f"{{{GLIB_NAMESPACE}}}get-property") is not None:
|
|
241
|
+
is_property_accessor = True
|
|
242
|
+
else:
|
|
243
|
+
tokens = name.split("_", maxsplit=1)
|
|
244
|
+
is_property_accessor = (
|
|
245
|
+
len(tokens) == 2
|
|
246
|
+
and tokens[0] in {"get", "set"}
|
|
247
|
+
and tokens[1] in c_prop_names
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
methods.append(
|
|
251
|
+
factory.method(
|
|
252
|
+
name,
|
|
253
|
+
c_identifier,
|
|
254
|
+
finish_c_identifier,
|
|
255
|
+
param_list,
|
|
256
|
+
throws,
|
|
257
|
+
retval,
|
|
258
|
+
is_property_accessor,
|
|
259
|
+
self,
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
return methods
|
|
263
|
+
|
|
264
|
+
@cached_property
|
|
265
|
+
def properties(self) -> List[Property]:
|
|
266
|
+
factory = self.model.factory
|
|
267
|
+
properties = []
|
|
268
|
+
custom = self.customizations
|
|
269
|
+
for element in self._properties:
|
|
270
|
+
name = element.get("name")
|
|
271
|
+
|
|
272
|
+
if custom is not None:
|
|
273
|
+
pcust = custom.properties.get(name, None)
|
|
274
|
+
if pcust is not None and pcust.drop:
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
c_name = name.replace("-", "_")
|
|
278
|
+
type = extract_type_from_entity(element, self.resolve_type)
|
|
279
|
+
if type.is_frida_options:
|
|
280
|
+
continue
|
|
281
|
+
writable = element.get("writable") == "1"
|
|
282
|
+
construct_only = element.get("construct-only") == "1"
|
|
283
|
+
|
|
284
|
+
getter = element.get("getter")
|
|
285
|
+
if getter is None:
|
|
286
|
+
getter = f"get_{c_name}"
|
|
287
|
+
|
|
288
|
+
setter = element.get("setter")
|
|
289
|
+
if setter is None and writable and not construct_only:
|
|
290
|
+
setter = f"set_{c_name}"
|
|
291
|
+
|
|
292
|
+
properties.append(
|
|
293
|
+
factory.property_(
|
|
294
|
+
name,
|
|
295
|
+
c_name,
|
|
296
|
+
type,
|
|
297
|
+
writable,
|
|
298
|
+
construct_only,
|
|
299
|
+
getter,
|
|
300
|
+
setter,
|
|
301
|
+
self,
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
return properties
|
|
305
|
+
|
|
306
|
+
@cached_property
|
|
307
|
+
def signals(self) -> List[Signal]:
|
|
308
|
+
factory = self.model.factory
|
|
309
|
+
signals = []
|
|
310
|
+
custom = self.customizations
|
|
311
|
+
for element in self._signals:
|
|
312
|
+
name = element.get("name")
|
|
313
|
+
|
|
314
|
+
if custom is not None:
|
|
315
|
+
scust = custom.signals.get(name, None)
|
|
316
|
+
if scust is not None and scust.drop:
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
c_name = name.replace("-", "_")
|
|
320
|
+
param_list = extract_parameters(
|
|
321
|
+
element.findall("./parameters/parameter", GIR_NAMESPACES),
|
|
322
|
+
nullable_implies_optional=False,
|
|
323
|
+
object_type=self,
|
|
324
|
+
resolve_type=self.resolve_type,
|
|
325
|
+
)
|
|
326
|
+
signals.append(factory.signal(name, c_name, param_list, self))
|
|
327
|
+
return signals
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@dataclass
|
|
331
|
+
class ClassObjectType(ObjectType):
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@dataclass
|
|
336
|
+
class InterfaceObjectType(ObjectType):
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@dataclass
|
|
341
|
+
class Procedure:
|
|
342
|
+
name: str
|
|
343
|
+
c_identifier: str
|
|
344
|
+
finish_c_identifier: Optional[str]
|
|
345
|
+
parameters: List[Parameter]
|
|
346
|
+
throws: bool
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def is_async(self) -> bool:
|
|
350
|
+
return self.finish_c_identifier is not None
|
|
351
|
+
|
|
352
|
+
@cached_property
|
|
353
|
+
def input_parameters(self) -> List[Parameter]:
|
|
354
|
+
return [p for p in self.parameters if p.direction != Direction.OUT]
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass
|
|
358
|
+
class Constructor(Procedure):
|
|
359
|
+
object_type: ObjectType
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass
|
|
363
|
+
class Method(Procedure):
|
|
364
|
+
return_value: Optional[ReturnValue]
|
|
365
|
+
is_property_accessor: bool
|
|
366
|
+
|
|
367
|
+
object_type: ObjectType
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@dataclass
|
|
371
|
+
class Property:
|
|
372
|
+
name: str
|
|
373
|
+
c_name: str
|
|
374
|
+
type: Type
|
|
375
|
+
writable: bool
|
|
376
|
+
construct_only: bool
|
|
377
|
+
getter: Optional[str]
|
|
378
|
+
setter: Optional[str]
|
|
379
|
+
|
|
380
|
+
object_type: ObjectType
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@dataclass
|
|
384
|
+
class Signal:
|
|
385
|
+
name: str
|
|
386
|
+
c_name: str
|
|
387
|
+
parameters: List[Parameter]
|
|
388
|
+
|
|
389
|
+
object_type: ObjectType
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
TransferOwnership = Enum("TransferOwnership", ["none", "full", "container"])
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@dataclass
|
|
396
|
+
class Parameter:
|
|
397
|
+
name: str
|
|
398
|
+
type: Type
|
|
399
|
+
optional: bool
|
|
400
|
+
nullable: bool
|
|
401
|
+
transfer_ownership: TransferOwnership
|
|
402
|
+
direction: Direction
|
|
403
|
+
|
|
404
|
+
object_type: ObjectType
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@dataclass
|
|
408
|
+
class ReturnValue:
|
|
409
|
+
type: Type
|
|
410
|
+
nullable: bool
|
|
411
|
+
transfer_ownership: TransferOwnership
|
|
412
|
+
|
|
413
|
+
object_type: ObjectType
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@dataclass
|
|
417
|
+
class Type:
|
|
418
|
+
name: str
|
|
419
|
+
nick: str
|
|
420
|
+
c: str
|
|
421
|
+
default_value: Optional[str]
|
|
422
|
+
copy_func: Optional[str]
|
|
423
|
+
destroy_func: Optional[str]
|
|
424
|
+
|
|
425
|
+
@cached_property
|
|
426
|
+
def is_frida_options(self) -> bool:
|
|
427
|
+
return self.c.startswith("Frida") and self.c.endswith("Options *")
|
|
428
|
+
|
|
429
|
+
@cached_property
|
|
430
|
+
def from_pointer_func(self) -> Optional[str]:
|
|
431
|
+
if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}:
|
|
432
|
+
return "GPOINTER_TO_SIZE"
|
|
433
|
+
if self.name in {"gint", "gint8", "gint16", "gint32"}:
|
|
434
|
+
return "GPOINTER_TO_INT"
|
|
435
|
+
if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}:
|
|
436
|
+
return "GPOINTER_TO_UINT"
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
@cached_property
|
|
440
|
+
def to_pointer_func(self) -> Optional[str]:
|
|
441
|
+
if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}:
|
|
442
|
+
return "GSIZE_TO_POINTER"
|
|
443
|
+
if self.name in {"gint", "gint8", "gint16", "gint32"}:
|
|
444
|
+
return "GINT_TO_POINTER"
|
|
445
|
+
if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}:
|
|
446
|
+
return "GUINT_TO_POINTER"
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class Direction(Enum):
|
|
451
|
+
IN = "in"
|
|
452
|
+
OUT = "out"
|
|
453
|
+
INOUT = "inout"
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@dataclass
|
|
457
|
+
class Enumeration:
|
|
458
|
+
name: str
|
|
459
|
+
c_type: str
|
|
460
|
+
get_type: str
|
|
461
|
+
_members: List[ET.Element]
|
|
462
|
+
|
|
463
|
+
model: Optional[Model]
|
|
464
|
+
|
|
465
|
+
@cached_property
|
|
466
|
+
def members(self) -> List[EnumerationMember]:
|
|
467
|
+
factory = self.model.factory
|
|
468
|
+
members = []
|
|
469
|
+
for element in self._members:
|
|
470
|
+
c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier")
|
|
471
|
+
members.append(
|
|
472
|
+
factory.enumeration_member(element.get("name"), c_identifier, self)
|
|
473
|
+
)
|
|
474
|
+
return members
|
|
475
|
+
|
|
476
|
+
@cached_property
|
|
477
|
+
def customizations(self):
|
|
478
|
+
return self.model.customizations.type_customizations.get(self.name)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@dataclass
|
|
482
|
+
class EnumerationMember:
|
|
483
|
+
name: str
|
|
484
|
+
c_identifier: Optional[str]
|
|
485
|
+
|
|
486
|
+
enumeration: Enumeration
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def parse_gir(
|
|
490
|
+
file_path: str, dependencies: Sequence[Model], factory: Factory
|
|
491
|
+
) -> Model:
|
|
492
|
+
tree = ET.parse(file_path)
|
|
493
|
+
|
|
494
|
+
el = tree.getroot().find("./namespace", GIR_NAMESPACES)
|
|
495
|
+
namespace = Namespace(
|
|
496
|
+
el.get("name"), el.get(f"{{{C_NAMESPACE}}}identifier-prefixes"), el
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
def resolve_type(name: str) -> Tuple[str, ET.Element]:
|
|
500
|
+
assert (
|
|
501
|
+
name not in PRIMITIVE_GIR_TYPES
|
|
502
|
+
), f"unexpectedly asked to resolve primitive type: {name}"
|
|
503
|
+
|
|
504
|
+
tokens = name.split(".", maxsplit=1)
|
|
505
|
+
if len(tokens) == 2:
|
|
506
|
+
ns_name, bare_name = tokens
|
|
507
|
+
if ns_name == namespace.name:
|
|
508
|
+
ns = namespace
|
|
509
|
+
else:
|
|
510
|
+
ns = next(
|
|
511
|
+
(
|
|
512
|
+
dep.namespace
|
|
513
|
+
for dep in dependencies
|
|
514
|
+
if dep.namespace.name == ns_name
|
|
515
|
+
),
|
|
516
|
+
None,
|
|
517
|
+
)
|
|
518
|
+
if ns is None:
|
|
519
|
+
assert ns is not None, f"unable to resolve namespace {ns_name}"
|
|
520
|
+
else:
|
|
521
|
+
ns = namespace
|
|
522
|
+
bare_name = name
|
|
523
|
+
qualified_name = f"{ns.name}.{bare_name}"
|
|
524
|
+
|
|
525
|
+
element = ns.type_elements.get(bare_name)
|
|
526
|
+
assert element is not None, f"unable to resolve type {bare_name}"
|
|
527
|
+
|
|
528
|
+
return (qualified_name, element)
|
|
529
|
+
|
|
530
|
+
object_types = OrderedDict()
|
|
531
|
+
|
|
532
|
+
for element in namespace.element.findall("./class", GIR_NAMESPACES):
|
|
533
|
+
name = element.get("name")
|
|
534
|
+
c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
535
|
+
get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
|
|
536
|
+
type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct")
|
|
537
|
+
if type_struct is not None:
|
|
538
|
+
type_struct = namespace.identifier_prefixes + type_struct
|
|
539
|
+
else:
|
|
540
|
+
type_struct = c_type + "Class"
|
|
541
|
+
parent = element.get("parent")
|
|
542
|
+
if parent is not None:
|
|
543
|
+
parent, _ = resolve_type(parent)
|
|
544
|
+
constructors = element.findall(".//constructor", GIR_NAMESPACES)
|
|
545
|
+
methods = element.findall(".//method", GIR_NAMESPACES)
|
|
546
|
+
properties = element.findall(".//property", GIR_NAMESPACES)
|
|
547
|
+
signals = element.findall(".//glib:signal", GIR_NAMESPACES)
|
|
548
|
+
implements = [
|
|
549
|
+
e.get("name") for e in element.findall(".//implements", GIR_NAMESPACES)
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
object_types[name] = factory.class_object_type(
|
|
553
|
+
name=name,
|
|
554
|
+
c_type=c_type,
|
|
555
|
+
get_type=get_type,
|
|
556
|
+
type_struct=type_struct,
|
|
557
|
+
parent=parent,
|
|
558
|
+
constructors=constructors,
|
|
559
|
+
methods=methods,
|
|
560
|
+
properties=properties,
|
|
561
|
+
signals=signals,
|
|
562
|
+
implements=implements,
|
|
563
|
+
resolve_type=resolve_type,
|
|
564
|
+
model=None,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
for element in namespace.element.findall("./interface", GIR_NAMESPACES):
|
|
568
|
+
name = element.get("name")
|
|
569
|
+
c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
570
|
+
get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
|
|
571
|
+
type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct")
|
|
572
|
+
if type_struct is not None:
|
|
573
|
+
type_struct = namespace.identifier_prefixes + type_struct
|
|
574
|
+
else:
|
|
575
|
+
type_struct = c_type + "Iface"
|
|
576
|
+
prereq = element.find(".//prerequisite", GIR_NAMESPACES)
|
|
577
|
+
parent = prereq.get("name") if prereq is not None else None
|
|
578
|
+
if parent is not None:
|
|
579
|
+
parent, _ = resolve_type(parent)
|
|
580
|
+
constructors = []
|
|
581
|
+
methods = element.findall(".//method", GIR_NAMESPACES)
|
|
582
|
+
properties = element.findall(".//property", GIR_NAMESPACES)
|
|
583
|
+
signals = element.findall(".//glib:signal", GIR_NAMESPACES)
|
|
584
|
+
|
|
585
|
+
object_types[name] = factory.interface_object_type(
|
|
586
|
+
name=name,
|
|
587
|
+
c_type=c_type,
|
|
588
|
+
get_type=get_type,
|
|
589
|
+
type_struct=type_struct,
|
|
590
|
+
parent=parent,
|
|
591
|
+
constructors=constructors,
|
|
592
|
+
methods=methods,
|
|
593
|
+
properties=properties,
|
|
594
|
+
signals=signals,
|
|
595
|
+
resolve_type=resolve_type,
|
|
596
|
+
model=None,
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
enumerations = OrderedDict()
|
|
600
|
+
error_domain = None
|
|
601
|
+
|
|
602
|
+
for element in namespace.element.findall("./enumeration", GIR_NAMESPACES):
|
|
603
|
+
enum_name = element.get("name")
|
|
604
|
+
enum_c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
605
|
+
get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
|
|
606
|
+
members = element.findall(".//member", GIR_NAMESPACES)
|
|
607
|
+
enumeration = factory.enumeration(
|
|
608
|
+
enum_name, enum_c_type, get_type, members, None
|
|
609
|
+
)
|
|
610
|
+
if element.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None:
|
|
611
|
+
error_domain = enumeration
|
|
612
|
+
continue
|
|
613
|
+
enumerations[enum_name] = enumeration
|
|
614
|
+
|
|
615
|
+
model = factory.model(
|
|
616
|
+
namespace,
|
|
617
|
+
object_types,
|
|
618
|
+
enumerations,
|
|
619
|
+
error_domain=error_domain,
|
|
620
|
+
factory=factory,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
for t in object_types.values():
|
|
624
|
+
t.model = model
|
|
625
|
+
for t in enumerations.values():
|
|
626
|
+
t.model = model
|
|
627
|
+
|
|
628
|
+
return model
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def extract_callable_details(
|
|
632
|
+
element: ET.Element,
|
|
633
|
+
result_element: ET.Element,
|
|
634
|
+
object_type: ObjectType,
|
|
635
|
+
resolve_type: ResolveTypeCallback,
|
|
636
|
+
) -> Tuple[str, Optional[str], List[Parameter], bool, bool, ET.Element]:
|
|
637
|
+
c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier")
|
|
638
|
+
|
|
639
|
+
parameters = element.findall("./parameters/parameter", GIR_NAMESPACES)
|
|
640
|
+
full_param_list = extract_parameters(
|
|
641
|
+
parameters,
|
|
642
|
+
nullable_implies_optional=True,
|
|
643
|
+
object_type=object_type,
|
|
644
|
+
resolve_type=resolve_type,
|
|
645
|
+
)
|
|
646
|
+
param_list = list(all_regular_parameters(full_param_list))
|
|
647
|
+
has_closure_param = any((param.get("closure") == "1" for param in parameters))
|
|
648
|
+
|
|
649
|
+
is_async = any(
|
|
650
|
+
param.type.name == "Gio.AsyncReadyCallback" for param in full_param_list
|
|
651
|
+
)
|
|
652
|
+
if not is_async:
|
|
653
|
+
result_element = element
|
|
654
|
+
|
|
655
|
+
finish_c_identifier = (
|
|
656
|
+
result_element.get(f"{{{C_NAMESPACE}}}identifier") if is_async else None
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
throws = result_element.get("throws") == "1"
|
|
660
|
+
|
|
661
|
+
return (
|
|
662
|
+
c_identifier,
|
|
663
|
+
finish_c_identifier,
|
|
664
|
+
param_list,
|
|
665
|
+
has_closure_param,
|
|
666
|
+
throws,
|
|
667
|
+
result_element,
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def extract_parameters(
|
|
672
|
+
parameter_elements: List[ET.Element],
|
|
673
|
+
nullable_implies_optional: bool,
|
|
674
|
+
object_type: ObjectType,
|
|
675
|
+
resolve_type: ResolveTypeCallback,
|
|
676
|
+
) -> List[Parameter]:
|
|
677
|
+
factory = object_type.model.factory
|
|
678
|
+
entries = []
|
|
679
|
+
for param in parameter_elements:
|
|
680
|
+
nullable = param.get("nullable") == "1"
|
|
681
|
+
entries.append((param, nullable))
|
|
682
|
+
|
|
683
|
+
last_required_index = None
|
|
684
|
+
for i, (param, nullable) in enumerate(entries):
|
|
685
|
+
optional = nullable and nullable_implies_optional
|
|
686
|
+
if not optional:
|
|
687
|
+
last_required_index = i
|
|
688
|
+
|
|
689
|
+
param_list = []
|
|
690
|
+
for i, (param, nullable) in enumerate(entries):
|
|
691
|
+
name = param.get("name")
|
|
692
|
+
type = extract_type_from_entity(param, resolve_type)
|
|
693
|
+
|
|
694
|
+
if last_required_index is None or i > last_required_index:
|
|
695
|
+
optional = nullable and nullable_implies_optional
|
|
696
|
+
else:
|
|
697
|
+
optional = False
|
|
698
|
+
|
|
699
|
+
ownership_val = param.get("transfer-ownership")
|
|
700
|
+
transfer_ownership = (
|
|
701
|
+
TransferOwnership[ownership_val]
|
|
702
|
+
if ownership_val is not None
|
|
703
|
+
else TransferOwnership.none
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
raw_direction = param.get("direction")
|
|
707
|
+
direction = (
|
|
708
|
+
Direction(raw_direction) if raw_direction is not None else Direction.IN
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
param_list.append(
|
|
712
|
+
factory.parameter(
|
|
713
|
+
name,
|
|
714
|
+
type,
|
|
715
|
+
optional,
|
|
716
|
+
nullable,
|
|
717
|
+
transfer_ownership,
|
|
718
|
+
direction,
|
|
719
|
+
object_type,
|
|
720
|
+
)
|
|
721
|
+
)
|
|
722
|
+
return param_list
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def all_regular_parameters(parameters: List[Parameter]) -> Iterator[Parameter]:
|
|
726
|
+
callback_index = None
|
|
727
|
+
for i, param in enumerate(parameters):
|
|
728
|
+
if param.type.name == "Gio.AsyncReadyCallback":
|
|
729
|
+
callback_index = i
|
|
730
|
+
continue
|
|
731
|
+
|
|
732
|
+
if callback_index is not None and i == callback_index + 1:
|
|
733
|
+
continue
|
|
734
|
+
|
|
735
|
+
yield param
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def extract_type_from_entity(
|
|
739
|
+
parent_element: ET.Element, resolve_type: ResolveTypeCallback
|
|
740
|
+
) -> Optional[Type]:
|
|
741
|
+
child = parent_element.find("type", GIR_NAMESPACES)
|
|
742
|
+
if child is None:
|
|
743
|
+
child = parent_element.find("array", GIR_NAMESPACES)
|
|
744
|
+
assert child is not None
|
|
745
|
+
element_type = extract_type_from_entity(child, resolve_type)
|
|
746
|
+
if element_type.name == "utf8":
|
|
747
|
+
return Type(
|
|
748
|
+
"utf8[]",
|
|
749
|
+
"strv",
|
|
750
|
+
"gchar **",
|
|
751
|
+
"NULL",
|
|
752
|
+
"g_strdupv",
|
|
753
|
+
"g_strfreev",
|
|
754
|
+
)
|
|
755
|
+
elif element_type.name == "gchar":
|
|
756
|
+
return Type("char[]", "chararray", "gchar *", "NULL", "NULL", "NULL")
|
|
757
|
+
elif element_type.name == "GObject.Value":
|
|
758
|
+
return Type("Value[]", "valuearray", "GValue *", "NULL", "NULL", "NULL")
|
|
759
|
+
else:
|
|
760
|
+
assert (
|
|
761
|
+
element_type.name == "guint8"
|
|
762
|
+
), f"unsupported array type: {element_type.name}"
|
|
763
|
+
return Type("uint8[]", "bytearray", "guint8 *", "NULL", "NULL", "NULL")
|
|
764
|
+
|
|
765
|
+
return parse_type(child, resolve_type)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def parse_type(
|
|
769
|
+
element: ET.Element, resolve_type: ResolveTypeCallback
|
|
770
|
+
) -> Optional[Type]:
|
|
771
|
+
name = element.get("name")
|
|
772
|
+
assert name is not None
|
|
773
|
+
if name == "none":
|
|
774
|
+
return None
|
|
775
|
+
|
|
776
|
+
is_primitive = name in PRIMITIVE_GIR_TYPES
|
|
777
|
+
c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
778
|
+
|
|
779
|
+
core_tag = None
|
|
780
|
+
if is_primitive:
|
|
781
|
+
type_element = element
|
|
782
|
+
if c_type is None:
|
|
783
|
+
c_type = name
|
|
784
|
+
else:
|
|
785
|
+
name, type_element = resolve_type(name)
|
|
786
|
+
if type_element.tag.startswith(CORE_TAG_PREFIX):
|
|
787
|
+
core_tag = type_element.tag[len(CORE_TAG_PREFIX) :]
|
|
788
|
+
c_type = type_element.get(f"{{{C_NAMESPACE}}}type")
|
|
789
|
+
if core_tag in {"class", "interface", "record"}:
|
|
790
|
+
c_type += "*"
|
|
791
|
+
|
|
792
|
+
nick = type_nick_from_name(name, element, resolve_type)
|
|
793
|
+
c = c_type.replace("*", " *")
|
|
794
|
+
|
|
795
|
+
default_value = "NULL" if "*" in c else None
|
|
796
|
+
|
|
797
|
+
if name == "utf8":
|
|
798
|
+
copy_func = "g_strdup"
|
|
799
|
+
destroy_func = "g_free"
|
|
800
|
+
elif name == "utf8[]":
|
|
801
|
+
copy_func = "g_strdupv"
|
|
802
|
+
destroy_func = "g_strfreev"
|
|
803
|
+
elif name == "GLib.HashTable":
|
|
804
|
+
copy_func = "g_hash_table_ref"
|
|
805
|
+
destroy_func = "g_hash_table_unref"
|
|
806
|
+
elif name == "GLib.Quark":
|
|
807
|
+
copy_func = None
|
|
808
|
+
destroy_func = None
|
|
809
|
+
elif name == "GObject.Value":
|
|
810
|
+
copy_func = "g_value_copy"
|
|
811
|
+
destroy_func = "g_value_reset"
|
|
812
|
+
elif name == "GObject.Closure":
|
|
813
|
+
copy_func = "g_closure_ref"
|
|
814
|
+
destroy_func = "g_closure_unref"
|
|
815
|
+
elif core_tag in {"class", "interface"}:
|
|
816
|
+
copy_func = "g_object_ref"
|
|
817
|
+
destroy_func = "g_object_unref"
|
|
818
|
+
elif is_primitive or core_tag in {"bitfield", "callback", "enumeration"}:
|
|
819
|
+
copy_func = None
|
|
820
|
+
destroy_func = None
|
|
821
|
+
else:
|
|
822
|
+
copy_func = type_element.get("copy-function")
|
|
823
|
+
destroy_func = type_element.get("free-function")
|
|
824
|
+
assert (
|
|
825
|
+
destroy_func is not None
|
|
826
|
+
), f"unable to resolve destroy function for {name}, core_tag={core_tag}"
|
|
827
|
+
|
|
828
|
+
return Type(name, nick, c, default_value, copy_func, destroy_func)
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def type_nick_from_name(
|
|
832
|
+
name: str, element: ET.Element, resolve_type: ResolveTypeCallback
|
|
833
|
+
) -> str:
|
|
834
|
+
if name == "GLib.PollFD":
|
|
835
|
+
return "pollfd"
|
|
836
|
+
|
|
837
|
+
tokens = name.split(".", maxsplit=1)
|
|
838
|
+
if len(tokens) == 1:
|
|
839
|
+
result = tokens[0]
|
|
840
|
+
if result.startswith("g"):
|
|
841
|
+
result = result[1:]
|
|
842
|
+
else:
|
|
843
|
+
result = to_snake_case(tokens[1])
|
|
844
|
+
|
|
845
|
+
if result == "hash_table":
|
|
846
|
+
key_type = parse_type(element[0], resolve_type)
|
|
847
|
+
value_type = parse_type(element[1], resolve_type)
|
|
848
|
+
assert (
|
|
849
|
+
key_type.name == "utf8" and value_type.name == "GLib.Variant"
|
|
850
|
+
), "only GHashTable<string, Variant> is supported for now"
|
|
851
|
+
result = "vardict"
|
|
852
|
+
|
|
853
|
+
return result
|