frida 16.7.14 → 16.7.15

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.
Files changed (172) hide show
  1. package/README.md +1 -10
  2. package/build/BSDmakefile +6 -0
  3. package/build/Makefile +10 -0
  4. package/build/src/frida.d.ts +364 -0
  5. package/build/src/frida.js +962 -0
  6. package/build/src/frida_binding.d.ts +938 -0
  7. package/meson.build +34 -67
  8. package/package.json +30 -20
  9. package/scripts/fetch-abi-bits.py +15 -65
  10. package/scripts/install.js +5 -4
  11. package/src/addon.def +3 -0
  12. package/src/addon.symbols +2 -1
  13. package/src/addon.version +4 -0
  14. package/src/frida_bindgen/__init__.py +0 -0
  15. package/src/frida_bindgen/__main__.py +4 -0
  16. package/src/frida_bindgen/__pycache__/__init__.cpython-312.pyc +0 -0
  17. package/src/frida_bindgen/__pycache__/__main__.cpython-312.pyc +0 -0
  18. package/src/frida_bindgen/__pycache__/cli.cpython-312.pyc +0 -0
  19. package/src/frida_bindgen/__pycache__/codegen.cpython-312.pyc +0 -0
  20. package/src/frida_bindgen/__pycache__/customization.cpython-312.pyc +0 -0
  21. package/src/frida_bindgen/__pycache__/loader.cpython-312.pyc +0 -0
  22. package/src/frida_bindgen/__pycache__/model.cpython-312.pyc +0 -0
  23. package/src/frida_bindgen/assets/codegen_helpers.c +1970 -0
  24. package/src/frida_bindgen/assets/codegen_helpers.ts +100 -0
  25. package/src/frida_bindgen/assets/codegen_prototypes.h +78 -0
  26. package/src/frida_bindgen/assets/codegen_types.h +57 -0
  27. package/src/frida_bindgen/assets/customization_facade.exports +13 -0
  28. package/src/frida_bindgen/assets/customization_facade.ts +157 -0
  29. package/src/frida_bindgen/assets/customization_helpers.imports +2 -0
  30. package/src/frida_bindgen/assets/customization_helpers.ts +396 -0
  31. package/src/frida_bindgen/cli.py +96 -0
  32. package/src/frida_bindgen/codegen.py +2233 -0
  33. package/src/frida_bindgen/customization.py +924 -0
  34. package/src/frida_bindgen/loader.py +60 -0
  35. package/src/frida_bindgen/model.py +1357 -0
  36. package/src/meson.build +92 -27
  37. package/{lib/build.py → src/tsc.py} +12 -12
  38. package/src/win_delay_load_hook.c +56 -0
  39. package/subprojects/frida-core.wrap +1 -1
  40. package/test/data/index.ts +2 -2
  41. package/test/device.ts +1 -2
  42. package/test/device_manager.ts +1 -2
  43. package/test/labrat.ts +2 -2
  44. package/test/script.ts +12 -12
  45. package/test/session.ts +3 -3
  46. package/tsconfig.json +6 -11
  47. package/dist/application.d.ts +0 -81
  48. package/dist/application.js +0 -2
  49. package/dist/authentication.d.ts +0 -3
  50. package/dist/authentication.js +0 -2
  51. package/dist/bus.d.ts +0 -16
  52. package/dist/bus.js +0 -23
  53. package/dist/cancellable.d.ts +0 -15
  54. package/dist/cancellable.js +0 -41
  55. package/dist/child.d.ts +0 -16
  56. package/dist/child.js +0 -9
  57. package/dist/crash.d.ts +0 -10
  58. package/dist/crash.js +0 -2
  59. package/dist/device.d.ts +0 -156
  60. package/dist/device.js +0 -188
  61. package/dist/device_manager.d.ts +0 -25
  62. package/dist/device_manager.js +0 -42
  63. package/dist/endpoint_parameters.d.ts +0 -26
  64. package/dist/endpoint_parameters.js +0 -24
  65. package/dist/icon.d.ts +0 -14
  66. package/dist/icon.js +0 -2
  67. package/dist/index.d.ts +0 -161
  68. package/dist/index.js +0 -170
  69. package/dist/iostream.d.ts +0 -13
  70. package/dist/iostream.js +0 -73
  71. package/dist/native.d.ts +0 -1
  72. package/dist/native.js +0 -11
  73. package/dist/portal_membership.d.ts +0 -6
  74. package/dist/portal_membership.js +0 -12
  75. package/dist/portal_service.d.ts +0 -48
  76. package/dist/portal_service.js +0 -52
  77. package/dist/process.d.ts +0 -47
  78. package/dist/process.js +0 -2
  79. package/dist/relay.d.ts +0 -22
  80. package/dist/relay.js +0 -32
  81. package/dist/script.d.ts +0 -70
  82. package/dist/script.js +0 -266
  83. package/dist/service.d.ts +0 -16
  84. package/dist/service.js +0 -26
  85. package/dist/session.d.ts +0 -45
  86. package/dist/session.js +0 -73
  87. package/dist/signals.d.ts +0 -20
  88. package/dist/signals.js +0 -40
  89. package/dist/socket_address.d.ts +0 -25
  90. package/dist/socket_address.js +0 -2
  91. package/dist/spawn.d.ts +0 -4
  92. package/dist/spawn.js +0 -2
  93. package/dist/system_parameters.d.ts +0 -84
  94. package/dist/system_parameters.js +0 -2
  95. package/lib/application.ts +0 -98
  96. package/lib/authentication.ts +0 -3
  97. package/lib/bus.ts +0 -30
  98. package/lib/cancellable.ts +0 -48
  99. package/lib/child.ts +0 -15
  100. package/lib/crash.ts +0 -11
  101. package/lib/device.ts +0 -331
  102. package/lib/device_manager.ts +0 -69
  103. package/lib/endpoint_parameters.ts +0 -56
  104. package/lib/icon.ts +0 -15
  105. package/lib/index.ts +0 -316
  106. package/lib/iostream.ts +0 -78
  107. package/lib/meson.build +0 -53
  108. package/lib/native.ts +0 -9
  109. package/lib/portal_membership.ts +0 -10
  110. package/lib/portal_service.ts +0 -105
  111. package/lib/process.ts +0 -57
  112. package/lib/relay.ts +0 -44
  113. package/lib/script.ts +0 -361
  114. package/lib/service.ts +0 -34
  115. package/lib/session.ts +0 -113
  116. package/lib/signals.ts +0 -45
  117. package/lib/socket_address.ts +0 -35
  118. package/lib/spawn.ts +0 -4
  119. package/lib/system_parameters.ts +0 -103
  120. package/meson.options +0 -11
  121. package/src/addon.cc +0 -78
  122. package/src/application.cc +0 -148
  123. package/src/application.h +0 -31
  124. package/src/authentication.cc +0 -174
  125. package/src/authentication.h +0 -24
  126. package/src/bus.cc +0 -167
  127. package/src/bus.h +0 -33
  128. package/src/cancellable.cc +0 -117
  129. package/src/cancellable.h +0 -31
  130. package/src/child.cc +0 -150
  131. package/src/child.h +0 -32
  132. package/src/crash.cc +0 -122
  133. package/src/crash.h +0 -30
  134. package/src/device.cc +0 -1350
  135. package/src/device.h +0 -56
  136. package/src/device_manager.cc +0 -362
  137. package/src/device_manager.h +0 -35
  138. package/src/endpoint_parameters.cc +0 -171
  139. package/src/endpoint_parameters.h +0 -28
  140. package/src/glib_context.cc +0 -62
  141. package/src/glib_context.h +0 -29
  142. package/src/glib_object.cc +0 -25
  143. package/src/glib_object.h +0 -37
  144. package/src/iostream.cc +0 -243
  145. package/src/iostream.h +0 -30
  146. package/src/operation.h +0 -94
  147. package/src/portal_membership.cc +0 -100
  148. package/src/portal_membership.h +0 -26
  149. package/src/portal_service.cc +0 -401
  150. package/src/portal_service.h +0 -40
  151. package/src/process.cc +0 -135
  152. package/src/process.h +0 -30
  153. package/src/relay.cc +0 -139
  154. package/src/relay.h +0 -31
  155. package/src/runtime.cc +0 -568
  156. package/src/runtime.h +0 -69
  157. package/src/script.cc +0 -301
  158. package/src/script.h +0 -36
  159. package/src/service.cc +0 -224
  160. package/src/service.h +0 -36
  161. package/src/session.cc +0 -860
  162. package/src/session.h +0 -42
  163. package/src/signals.cc +0 -334
  164. package/src/signals.h +0 -47
  165. package/src/spawn.cc +0 -95
  166. package/src/spawn.h +0 -27
  167. package/src/usage_monitor.h +0 -117
  168. package/src/uv_context.cc +0 -118
  169. package/src/uv_context.h +0 -40
  170. package/src/win_delay_load_hook.cc +0 -63
  171. package/subprojects/nan.wrap +0 -9
  172. package/subprojects/packagefiles/nan.patch +0 -13
@@ -0,0 +1,1357 @@
1
+ from __future__ import annotations
2
+
3
+ import xml.etree.ElementTree as ET
4
+ from collections import OrderedDict, defaultdict
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from functools import cached_property
8
+ from typing import (Callable, Iterator, List, Mapping, Optional, Sequence,
9
+ Tuple, Union)
10
+
11
+ CORE_NAMESPACE = "http://www.gtk.org/introspection/core/1.0"
12
+ C_NAMESPACE = "http://www.gtk.org/introspection/c/1.0"
13
+ GLIB_NAMESPACE = "http://www.gtk.org/introspection/glib/1.0"
14
+ GIR_NAMESPACES = {"": CORE_NAMESPACE, "glib": GLIB_NAMESPACE}
15
+
16
+ CORE_TAG_PREFIX = f"{{{CORE_NAMESPACE}}}"
17
+
18
+ NUMERIC_GIR_TYPES = {
19
+ "gsize",
20
+ "gssize",
21
+ "gint",
22
+ "guint",
23
+ "glong",
24
+ "gulong",
25
+ "gint8",
26
+ "gint16",
27
+ "gint32",
28
+ "gint64",
29
+ "guint8",
30
+ "guint16",
31
+ "guint32",
32
+ "guint64",
33
+ "GType",
34
+ "GQuark",
35
+ }
36
+
37
+ PRIMITIVE_GIR_TYPES = NUMERIC_GIR_TYPES | {
38
+ "gpointer",
39
+ "gboolean",
40
+ "gchar",
41
+ "utf8",
42
+ "utf8[]",
43
+ }
44
+
45
+ ResolveTypeCallback = Callable[[str], Tuple[str, ET.Element]]
46
+
47
+
48
+ @dataclass
49
+ class Customizations:
50
+ custom_types: Mapping[str, CustomType] = field(default_factory=OrderedDict)
51
+ type_customizations: Mapping[str, TypeCustomizations] = field(
52
+ default_factory=OrderedDict
53
+ )
54
+ facade_exports: List[str] = field(default_factory=list)
55
+ facade_code: str = ""
56
+ helper_imports: List[str] = field(default_factory=list)
57
+ helper_code: str = ""
58
+
59
+
60
+ @dataclass
61
+ class Model:
62
+ namespace: Namespace
63
+ _object_types: OrderedDict[str, ObjectType]
64
+ enumerations: OrderedDict[str, Enumeration]
65
+ customizations: Customizations = field(default_factory=Customizations)
66
+
67
+ @cached_property
68
+ def object_types(self) -> OrderedDict[str, ObjectType]:
69
+ result = OrderedDict()
70
+ type_customizations = self.customizations.type_customizations
71
+ for k, v in self._object_types.items():
72
+ custom = type_customizations.get(k)
73
+ if custom is None or not custom.drop:
74
+ result[k] = v
75
+ return result
76
+
77
+ @cached_property
78
+ def public_types(self) -> OrderedDict[str, Union[ObjectType, Enumeration]]:
79
+ return OrderedDict(
80
+ [(k, v) for k, v in self.object_types.items() if v.is_public]
81
+ + list(self.enumerations.items())
82
+ )
83
+
84
+ @cached_property
85
+ def interface_types_with_abstract_base(self) -> List[InterfaceObjectType]:
86
+ return [
87
+ t
88
+ for t in self.object_types.values()
89
+ if isinstance(t, InterfaceObjectType) and t.has_abstract_base
90
+ ]
91
+
92
+ def resolve_object_type(self, name: str) -> ObjectType:
93
+ bare_name = name.split(".", maxsplit=1)[-1]
94
+ return self.object_types[bare_name]
95
+
96
+ def resolve_js_type(self, t: Type) -> str:
97
+ js = js_type_from_gir(t.name)
98
+ otype = self.object_types.get(js)
99
+ if otype is not None:
100
+ return otype.js_name
101
+ return js
102
+
103
+
104
+ @dataclass
105
+ class Namespace:
106
+ name: str
107
+ identifier_prefixes: str
108
+ element: ET.Element
109
+
110
+ @cached_property
111
+ def type_elements(self) -> Mapping[str, ET.Element]:
112
+ result = {}
113
+ for toplevel in self.element.findall("./*[@name]", GIR_NAMESPACES):
114
+ name = toplevel.get("name")
115
+ result[name] = toplevel
116
+ for callback in toplevel.findall("./callback", GIR_NAMESPACES):
117
+ result[name + callback.get("name")] = callback
118
+ return result
119
+
120
+
121
+ @dataclass
122
+ class ObjectType:
123
+ name: str
124
+ c_type: str
125
+ get_type: str
126
+ type_struct: str
127
+ _parent: Optional[str]
128
+ _constructors: List[ET.Element]
129
+ _methods: List[ET.Element]
130
+ _properties: List[ET.Element]
131
+ _signals: List[ET.Element]
132
+ resolve_type: ResolveTypeCallback
133
+
134
+ model: Optional[Model]
135
+
136
+ @cached_property
137
+ def js_name(self) -> str:
138
+ custom = self.customizations
139
+ if custom is not None and custom.js_name is not None:
140
+ return custom.js_name
141
+ return self.name
142
+
143
+ @cached_property
144
+ def prefixed_js_name(self) -> str:
145
+ return f"_{self.js_name}" if self.needs_wrapper else self.js_name
146
+
147
+ @cached_property
148
+ def abstract_base_c_type(self) -> str:
149
+ return f"FdnAbstract{self.name}"
150
+
151
+ @cached_property
152
+ def parent(self) -> ObjectType:
153
+ if self._parent is None:
154
+ return None
155
+ return self.model.resolve_object_type(self._parent)
156
+
157
+ @property
158
+ def is_public(self) -> bool:
159
+ return not self.is_frida_list
160
+
161
+ @cached_property
162
+ def is_frida_options(self) -> bool:
163
+ return self.c_type.startswith("Frida") and self.c_type.endswith("Options")
164
+
165
+ @cached_property
166
+ def is_frida_list(self) -> bool:
167
+ return self.c_type.startswith("Frida") and self.c_type.endswith("List")
168
+
169
+ @cached_property
170
+ def needs_wrapper(self) -> bool:
171
+ custom = self.customizations
172
+ if custom is None:
173
+ return False
174
+ if custom.custom_code is not None:
175
+ return True
176
+ ctor = self.constructors[0] if self.constructors else None
177
+ if ctor is not None and ctor.needs_wrapper:
178
+ return True
179
+ return self.wrapped_methods or self.wrapped_signals
180
+
181
+ @cached_property
182
+ def customizations(self) -> Optional[ObjectTypeCustomizations]:
183
+ return self.model.customizations.type_customizations.get(self.name)
184
+
185
+ @cached_property
186
+ def c_symbol_prefix(self) -> str:
187
+ return f"fdn_{to_snake_case(self.name)}"
188
+
189
+ @cached_property
190
+ def abstract_base_c_symbol_prefix(self) -> str:
191
+ return f"fdn_abstract_{to_snake_case(self.name)}"
192
+
193
+ @cached_property
194
+ def c_cast_macro(self) -> str:
195
+ return to_macro_case(self.c_type)
196
+
197
+ @cached_property
198
+ def abstract_base_c_cast_macro(self) -> str:
199
+ return to_macro_case(self.abstract_base_c_type)
200
+
201
+ @cached_property
202
+ def constructors(self) -> List[Constructor]:
203
+ constructors = []
204
+ custom = self.customizations
205
+ for element in self._constructors:
206
+ if element.get("introspectable") == "0" or element.get("deprecated") == "1":
207
+ continue
208
+
209
+ name = element.get("name")
210
+
211
+ if custom is not None:
212
+ ccust = custom.constructor
213
+ if ccust is not None and ccust.drop:
214
+ continue
215
+
216
+ (
217
+ c_identifier,
218
+ finish_c_identifier,
219
+ param_list,
220
+ has_closure_param,
221
+ throws,
222
+ result_element,
223
+ ) = extract_callable_details(element, element, self, self.resolve_type)
224
+ if has_closure_param or finish_c_identifier is not None:
225
+ continue
226
+
227
+ constructors.append(
228
+ Constructor(
229
+ name, c_identifier, finish_c_identifier, param_list, throws, self
230
+ )
231
+ )
232
+ return constructors
233
+
234
+ @cached_property
235
+ def methods(self) -> List[Method]:
236
+ methods = []
237
+ c_prop_names = {prop.c_name for prop in self.properties}
238
+ custom = self.customizations
239
+ for element in self._methods:
240
+ name = element.get("name")
241
+
242
+ if (
243
+ element.get("introspectable") == "0"
244
+ or name.startswith("_")
245
+ or name.endswith("_sync")
246
+ or name.endswith("_finish")
247
+ ):
248
+ continue
249
+
250
+ if custom is not None:
251
+ mcust = custom.methods.get(name, None)
252
+ if mcust is not None and mcust.drop:
253
+ continue
254
+
255
+ finish_func = element.get(f"{{{GLIB_NAMESPACE}}}finish-func")
256
+ if finish_func is None:
257
+ finish_func = f"{name}_finish"
258
+ result_element = next(
259
+ (m for m in self._methods if m.get("name") == finish_func), element
260
+ )
261
+
262
+ (
263
+ c_identifier,
264
+ finish_c_identifier,
265
+ param_list,
266
+ has_closure_param,
267
+ throws,
268
+ result_element,
269
+ ) = extract_callable_details(
270
+ element, result_element, self, self.resolve_type
271
+ )
272
+ if has_closure_param:
273
+ continue
274
+
275
+ retval_element = result_element.find(".//return-value", GIR_NAMESPACES)
276
+ rettype = extract_type_from_entity(retval_element, self.resolve_type)
277
+ if rettype is not None:
278
+ if rettype.is_frida_options:
279
+ continue
280
+
281
+ nullable = retval_element.get("nullable") == "1"
282
+
283
+ ownership_val = retval_element.get("transfer-ownership")
284
+ transfer_ownership = (
285
+ TransferOwnership[ownership_val]
286
+ if ownership_val is not None
287
+ else TransferOwnership.none
288
+ )
289
+
290
+ retval = ReturnValue(rettype, nullable, transfer_ownership, self)
291
+ else:
292
+ retval = None
293
+
294
+ if element.get(f"{{{GLIB_NAMESPACE}}}get-property") is not None:
295
+ is_property_accessor = True
296
+ else:
297
+ tokens = name.split("_", maxsplit=1)
298
+ is_property_accessor = (
299
+ len(tokens) == 2
300
+ and tokens[0] in {"get", "set"}
301
+ and tokens[1] in c_prop_names
302
+ )
303
+
304
+ methods.append(
305
+ Method(
306
+ name,
307
+ c_identifier,
308
+ finish_c_identifier,
309
+ param_list,
310
+ throws,
311
+ retval,
312
+ is_property_accessor,
313
+ self,
314
+ )
315
+ )
316
+ return methods
317
+
318
+ @cached_property
319
+ def wrapped_methods(self) -> List[Method]:
320
+ return [m for m in self.methods if m.needs_wrapper]
321
+
322
+ @cached_property
323
+ def properties(self) -> List[Property]:
324
+ properties = []
325
+ custom = self.customizations
326
+ for element in self._properties:
327
+ name = element.get("name")
328
+
329
+ if custom is not None:
330
+ pcust = custom.properties.get(name, None)
331
+ if pcust is not None and pcust.drop:
332
+ continue
333
+
334
+ c_name = name.replace("-", "_")
335
+ type = extract_type_from_entity(element, self.resolve_type)
336
+ if type.is_frida_options:
337
+ continue
338
+ writable = element.get("writable") == "1"
339
+ construct_only = element.get("construct-only") == "1"
340
+
341
+ getter = element.get("getter")
342
+ if getter is None:
343
+ getter = f"get_{c_name}"
344
+
345
+ setter = element.get("setter")
346
+ if setter is None and writable and not construct_only:
347
+ setter = f"set_{c_name}"
348
+
349
+ properties.append(
350
+ Property(
351
+ name,
352
+ c_name,
353
+ type,
354
+ writable,
355
+ construct_only,
356
+ getter,
357
+ setter,
358
+ self,
359
+ )
360
+ )
361
+ return properties
362
+
363
+ @cached_property
364
+ def signals(self) -> List[Signal]:
365
+ signals = []
366
+ custom = self.customizations
367
+ for element in self._signals:
368
+ name = element.get("name")
369
+
370
+ if custom is not None:
371
+ scust = custom.signals.get(name, None)
372
+ if scust is not None and scust.drop:
373
+ continue
374
+
375
+ c_name = name.replace("-", "_")
376
+ param_list = extract_parameters(
377
+ element.findall("./parameters/parameter", GIR_NAMESPACES),
378
+ nullable_implies_optional=False,
379
+ object_type=self,
380
+ resolve_type=self.resolve_type,
381
+ )
382
+ signals.append(Signal(name, c_name, param_list, self))
383
+ return signals
384
+
385
+ @cached_property
386
+ def wrapped_signals(self) -> List[Signal]:
387
+ return [s for s in self.signals if s.needs_wrapper]
388
+
389
+
390
+ @dataclass
391
+ class ClassObjectType(ObjectType):
392
+ _implements: List[str]
393
+
394
+ @cached_property
395
+ def implements(self) -> List[InterfaceObjectType]:
396
+ return [self.model.resolve_object_type(i) for i in self._implements]
397
+
398
+
399
+ @dataclass
400
+ class InterfaceObjectType(ObjectType):
401
+ @cached_property
402
+ def has_abstract_base(self) -> bool:
403
+ custom = self.customizations
404
+ if custom is None:
405
+ return True
406
+ return not custom.drop_abstract_base
407
+
408
+
409
+ @dataclass
410
+ class Procedure:
411
+ name: str
412
+ c_identifier: str
413
+ finish_c_identifier: Optional[str]
414
+ parameters: List[Parameter]
415
+ throws: bool
416
+
417
+ @property
418
+ def is_async(self) -> bool:
419
+ return self.finish_c_identifier is not None
420
+
421
+ @cached_property
422
+ def input_parameters(self) -> List[Parameter]:
423
+ return [p for p in self.parameters if p.direction != Direction.OUT]
424
+
425
+
426
+ @dataclass
427
+ class Constructor(Procedure):
428
+ object_type: ObjectType
429
+
430
+ @cached_property
431
+ def param_typings(self) -> List[str]:
432
+ custom = self.customizations
433
+ if custom is not None and custom.param_typings is not None:
434
+ return custom.param_typings
435
+ return [param.typing for param in self.parameters]
436
+
437
+ @property
438
+ def needs_wrapper(self) -> bool:
439
+ custom = self.customizations
440
+ if custom is None:
441
+ return False
442
+ return custom.custom_logic is not None
443
+
444
+ @cached_property
445
+ def customizations(self) -> Optional[ConstructorCustomizations]:
446
+ custom = self.object_type.customizations
447
+ if custom is None:
448
+ return None
449
+ return custom.constructor
450
+
451
+
452
+ @dataclass
453
+ class Method(Procedure):
454
+ return_value: Optional[ReturnValue]
455
+ is_property_accessor: bool
456
+
457
+ object_type: ObjectType
458
+
459
+ @cached_property
460
+ def js_name(self) -> str:
461
+ custom = self.customizations
462
+ if custom is not None and custom.js_name is not None:
463
+ return custom.js_name
464
+ return to_camel_case(self.name)
465
+
466
+ @cached_property
467
+ def prefixed_js_name(self) -> str:
468
+ custom = self.customizations
469
+ if self.needs_wrapper or (custom is not None and custom.hide):
470
+ return f"_{self.js_name}"
471
+ return self.js_name
472
+
473
+ @cached_property
474
+ def cself_name(self) -> str:
475
+ return to_snake_case(self.object_type.name).split("_")[-1]
476
+
477
+ @cached_property
478
+ def param_ctypings(self) -> List[str]:
479
+ result = [f"{self.object_type.c_type} * {self.cself_name}"]
480
+ result += [param.ctyping for param in self.parameters]
481
+ if self.is_async:
482
+ result += ["GAsyncReadyCallback callback", "gpointer user_data"]
483
+ return result
484
+
485
+ @cached_property
486
+ def finish_param_ctypings(self) -> List[str]:
487
+ result = [
488
+ f"{self.object_type.c_type} * {self.cself_name}",
489
+ "GAsyncResult * result",
490
+ ]
491
+ if self.throws:
492
+ result.append("GError ** error")
493
+ return result
494
+
495
+ @cached_property
496
+ def param_typings(self) -> List[str]:
497
+ custom = self.customizations
498
+ if custom is not None and custom.param_typings is not None:
499
+ return custom.param_typings
500
+ return self.prefixed_param_typings
501
+
502
+ @cached_property
503
+ def prefixed_param_typings(self) -> List[str]:
504
+ return [param.typing for param in self.input_parameters]
505
+
506
+ @cached_property
507
+ def return_ctyping(self) -> str:
508
+ retval = self.return_value
509
+ return retval.ctyping if retval is not None else "void"
510
+
511
+ @cached_property
512
+ def return_typing(self) -> str:
513
+ custom = self.customizations
514
+ if custom is not None and custom.return_typing is not None:
515
+ return custom.return_typing
516
+ return self.prefixed_return_typing
517
+
518
+ @cached_property
519
+ def prefixed_return_typing(self) -> str:
520
+ retval = self.return_value
521
+ typing = retval.typing if retval is not None else "void"
522
+ return f"Promise<{typing}>" if self.is_async else typing
523
+
524
+ @property
525
+ def needs_wrapper(self) -> bool:
526
+ custom = self.customizations
527
+ if custom is None:
528
+ return False
529
+ return custom.custom_logic is not None or custom.return_wrapper is not None
530
+
531
+ @cached_property
532
+ def customizations(self) -> Optional[MethodCustomizations]:
533
+ custom = self.object_type.customizations
534
+ if custom is None:
535
+ return None
536
+ return custom.methods.get(self.name)
537
+
538
+ @cached_property
539
+ def operation_type_name(self) -> str:
540
+ return f"Fdn{self.object_type.name}{to_pascal_case(self.name)}Operation"
541
+
542
+ @cached_property
543
+ def abstract_base_operation_type_name(self) -> str:
544
+ return f"FdnAbstract{self.object_type.name}{to_pascal_case(self.name)}Operation"
545
+
546
+ @cached_property
547
+ def is_select_method(self) -> bool:
548
+ return self.name.startswith("select_") or self.name.startswith("add_")
549
+
550
+ @cached_property
551
+ def select_noun(self) -> str:
552
+ assert (
553
+ self.is_select_method
554
+ ), "select_noun can only be called on selector methods"
555
+ return self.name.split("_", maxsplit=1)[1]
556
+
557
+ @cached_property
558
+ def select_plural_noun(self) -> str:
559
+ return f"{self.select_noun}s"
560
+
561
+ @cached_property
562
+ def select_element_type(self) -> Type:
563
+ assert (
564
+ self.is_select_method
565
+ ), "select_element_type can only be called on selector methods"
566
+ return self.parameters[0].type
567
+
568
+
569
+ @dataclass
570
+ class Property:
571
+ name: str
572
+ c_name: str
573
+ type: Type
574
+ writable: bool
575
+ construct_only: bool
576
+ getter: Optional[str]
577
+ setter: Optional[str]
578
+
579
+ object_type: ObjectType
580
+
581
+ @cached_property
582
+ def js_name(self) -> str:
583
+ custom = self.customizations
584
+ if custom is not None and custom.js_name is not None:
585
+ return custom.js_name
586
+ return to_camel_case(self.c_name)
587
+
588
+ @cached_property
589
+ def typing(self) -> str:
590
+ custom = self.customizations
591
+ if custom is not None and custom.typing is not None:
592
+ return custom.typing
593
+ readonly = "readonly " if not self.writable else ""
594
+ optional_str = "?" if self.object_type.is_frida_options else ""
595
+ return f"{readonly}{self.js_name}{optional_str}: {self.object_type.model.resolve_js_type(self.type)}"
596
+
597
+ @cached_property
598
+ def customizations(self) -> Optional[PropertyCustomizations]:
599
+ custom = self.object_type.customizations
600
+ if custom is None:
601
+ return None
602
+ return custom.properties.get(self.name)
603
+
604
+
605
+ @dataclass
606
+ class Signal:
607
+ name: str
608
+ c_name: str
609
+ parameters: List[Parameter]
610
+
611
+ object_type: ObjectType
612
+
613
+ @cached_property
614
+ def js_name(self) -> str:
615
+ return to_camel_case(self.c_name)
616
+
617
+ @cached_property
618
+ def prefixed_js_name(self) -> str:
619
+ return f"_{self.js_name}" if self.needs_wrapper else self.js_name
620
+
621
+ @cached_property
622
+ def handler_type_name(self) -> str:
623
+ # XXX: Special-cases to avoid breaking API:
624
+ class_name = self.object_type.name
625
+ if class_name == "DeviceManager":
626
+ prefix = "Device"
627
+ elif class_name == "Device":
628
+ prefix = "Device" if self.name == "lost" else ""
629
+ elif class_name == "PortalService":
630
+ prefix = "Portal"
631
+ elif class_name == "Cancellable":
632
+ prefix = ""
633
+ else:
634
+ prefix = class_name
635
+ return f"{prefix}{to_pascal_case(self.c_name)}Handler"
636
+
637
+ @cached_property
638
+ def prefixed_handler_type_name(self) -> str:
639
+ return (
640
+ f"_{self.handler_type_name}"
641
+ if self.needs_wrapper
642
+ else self.handler_type_name
643
+ )
644
+
645
+ @cached_property
646
+ def typing(self) -> str:
647
+ params = ", ".join([p.typing for p in self.parameters])
648
+ return f"({params}) => void"
649
+
650
+ @property
651
+ def needs_wrapper(self) -> bool:
652
+ custom = self.customizations
653
+ if custom is None:
654
+ return False
655
+ return custom.transform is not None or custom.intercept is not None
656
+
657
+ @cached_property
658
+ def customizations(self) -> Optional[SignalCustomizations]:
659
+ custom = self.object_type.customizations
660
+ if custom is None:
661
+ return None
662
+ return custom.signals.get(self.name)
663
+
664
+
665
+ TransferOwnership = Enum("TransferOwnership", ["none", "full", "container"])
666
+
667
+
668
+ @dataclass
669
+ class Parameter:
670
+ name: str
671
+ type: Type
672
+ optional: bool
673
+ nullable: bool
674
+ transfer_ownership: TransferOwnership
675
+ direction: Direction
676
+
677
+ object_type: ObjectType
678
+
679
+ @cached_property
680
+ def js_name(self) -> str:
681
+ return to_camel_case(self.name)
682
+
683
+ @cached_property
684
+ def ctyping(self) -> str:
685
+ return f"{self.type.c} {self.name}"
686
+
687
+ @cached_property
688
+ def typing(self) -> str:
689
+ optional_str = "?" if self.optional else ""
690
+ t = f"{self.js_name}{optional_str}: {self.object_type.model.resolve_js_type(self.type)}"
691
+ if self.nullable and not self.type.is_frida_options:
692
+ t += " | null"
693
+ return t
694
+
695
+ @cached_property
696
+ def copy_func(self) -> Optional[str]:
697
+ return self.type.copy_func
698
+
699
+ @cached_property
700
+ def destroy_func(self) -> Optional[str]:
701
+ return self.type.destroy_func
702
+
703
+
704
+ @dataclass
705
+ class ReturnValue:
706
+ type: Type
707
+ nullable: bool
708
+ transfer_ownership: TransferOwnership
709
+
710
+ object_type: ObjectType
711
+
712
+ @cached_property
713
+ def ctyping(self) -> str:
714
+ return self.type.c
715
+
716
+ @cached_property
717
+ def typing(self) -> str:
718
+ t = self.object_type.model.resolve_js_type(self.type)
719
+ if self.nullable:
720
+ t += " | null"
721
+ return t
722
+
723
+ @cached_property
724
+ def destroy_func(self) -> Optional[str]:
725
+ if self.transfer_ownership == TransferOwnership.none:
726
+ return None
727
+ return self.type.destroy_func
728
+
729
+
730
+ @dataclass
731
+ class Type:
732
+ name: str
733
+ nick: str
734
+ c: str
735
+ default_value: Optional[str]
736
+ copy_func: Optional[str]
737
+ destroy_func: Optional[str]
738
+
739
+ @cached_property
740
+ def from_pointer_func(self) -> Optional[str]:
741
+ if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}:
742
+ return "GPOINTER_TO_SIZE"
743
+ if self.name in {"gint", "gint8", "gint16", "gint32"}:
744
+ return "GPOINTER_TO_INT"
745
+ if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}:
746
+ return "GPOINTER_TO_UINT"
747
+ return None
748
+
749
+ @cached_property
750
+ def to_pointer_func(self) -> Optional[str]:
751
+ if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}:
752
+ return "GSIZE_TO_POINTER"
753
+ if self.name in {"gint", "gint8", "gint16", "gint32"}:
754
+ return "GINT_TO_POINTER"
755
+ if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}:
756
+ return "GUINT_TO_POINTER"
757
+ return None
758
+
759
+ @cached_property
760
+ def is_frida_options(self) -> bool:
761
+ return self.c.startswith("Frida") and self.c.endswith("Options *")
762
+
763
+
764
+ class Direction(Enum):
765
+ IN = "in"
766
+ OUT = "out"
767
+ INOUT = "inout"
768
+
769
+
770
+ @dataclass
771
+ class Enumeration:
772
+ name: str
773
+ c_type: str
774
+ get_type: str
775
+ _members: List[ET.Element]
776
+
777
+ model: Optional[Model]
778
+
779
+ @property
780
+ def js_name(self) -> str:
781
+ return self.name
782
+
783
+ @property
784
+ def prefixed_js_name(self) -> str:
785
+ return self.name
786
+
787
+ @cached_property
788
+ def members(self) -> List[EnumerationMember]:
789
+ members = []
790
+ for element in self._members:
791
+ members.append(EnumerationMember(element.get("name"), self))
792
+ return members
793
+
794
+ @property
795
+ def is_frida_options(self) -> bool:
796
+ return False
797
+
798
+ @cached_property
799
+ def customizations(self) -> Optional[EnumerationCustomizations]:
800
+ return self.model.customizations.type_customizations.get(self.name)
801
+
802
+ @cached_property
803
+ def c_symbol_prefix(self) -> str:
804
+ return f"fdn_{to_snake_case(self.name)}"
805
+
806
+
807
+ @dataclass
808
+ class EnumerationMember:
809
+ name: str
810
+
811
+ enumeration: Enumeration
812
+
813
+ @cached_property
814
+ def js_name(self) -> str:
815
+ custom = self.customizations
816
+ if custom is not None and custom.js_name is not None:
817
+ return custom.js_name
818
+ return to_pascal_case(self.name)
819
+
820
+ @cached_property
821
+ def nick(self) -> str:
822
+ return self.name.replace("_", "-")
823
+
824
+ @cached_property
825
+ def customizations(self) -> Optional[EnumerationMemberCustomizations]:
826
+ custom = self.enumeration.customizations
827
+ if custom is None:
828
+ return None
829
+ return custom.members.get(self.name)
830
+
831
+
832
+ @dataclass
833
+ class CustomType:
834
+ kind: CustomTypeKind
835
+ typing: str
836
+
837
+
838
+ class CustomTypeKind(Enum):
839
+ TYPE = "type"
840
+ INTERFACE = "interface"
841
+ ENUM = "enum"
842
+
843
+
844
+ @dataclass
845
+ class TypeCustomizations:
846
+ pass
847
+
848
+
849
+ @dataclass
850
+ class ObjectTypeCustomizations(TypeCustomizations):
851
+ js_name: Optional[str] = None
852
+ drop: bool = False
853
+ drop_abstract_base: bool = False
854
+ constructor: Optional[ConstructorCustomizations] = None
855
+ methods: Mapping[str, MethodCustomizations] = field(
856
+ default_factory=lambda: defaultdict(dict)
857
+ )
858
+ properties: Mapping[str, PropertyCustomizations] = field(
859
+ default_factory=lambda: defaultdict(dict)
860
+ )
861
+ signals: Mapping[str, SignalCustomizations] = field(
862
+ default_factory=lambda: defaultdict(dict)
863
+ )
864
+ custom_code: Optional[CustomCode] = None
865
+ cleanup: Optional[str] = None
866
+ keep_alive: Optional[KeepAliveCustomization] = None
867
+
868
+
869
+ @dataclass
870
+ class KeepAliveCustomization:
871
+ is_destroyed_function: str
872
+ destroy_signal_name: str
873
+
874
+
875
+ @dataclass
876
+ class ConstructorCustomizations:
877
+ drop: bool = False
878
+ param_typings: Optional[List[str]] = None
879
+ custom_logic: Optional[str] = None
880
+
881
+
882
+ @dataclass
883
+ class MethodCustomizations:
884
+ js_name: Optional[str] = None
885
+ drop: bool = False
886
+ hide: bool = False
887
+ param_typings: Optional[List[str]] = None
888
+ return_typing: Optional[str] = None
889
+ custom_logic: Optional[str] = None
890
+ return_wrapper: Optional[str] = None
891
+ return_cconversion: Optional[str] = None
892
+ ref_keep_alive: bool = False
893
+ unref_keep_alive: bool = False
894
+
895
+
896
+ @dataclass
897
+ class PropertyCustomizations:
898
+ js_name: Optional[str] = None
899
+ drop: bool = False
900
+ typing: Optional[str] = None
901
+
902
+
903
+ @dataclass
904
+ class SignalCustomizations:
905
+ drop: bool = False
906
+ behavior: str = "FDN_SIGNAL_ALLOW_EXIT"
907
+ transform: Optional[Mapping[int, Tuple[str, Optional[str]]]] = None
908
+ intercept: Optional[str] = None
909
+
910
+
911
+ @dataclass
912
+ class CustomCode:
913
+ declarations: List[CustomDeclaration] = field(default_factory=list)
914
+ methods: List[CustomMethod] = field(default_factory=list)
915
+
916
+
917
+ @dataclass
918
+ class CustomDeclaration:
919
+ typing: Optional[str]
920
+ code: str
921
+
922
+
923
+ @dataclass
924
+ class CustomMethod:
925
+ typing: Optional[str]
926
+ code: str
927
+
928
+
929
+ @dataclass
930
+ class EnumerationCustomizations(TypeCustomizations):
931
+ members: Mapping[str, EnumerationMemberCustomizations] = field(
932
+ default_factory=lambda: defaultdict(dict)
933
+ )
934
+
935
+
936
+ @dataclass
937
+ class EnumerationMemberCustomizations:
938
+ js_name: Optional[str] = None
939
+
940
+
941
+ def parse_gir(file_path: str, dependencies: Sequence[Model]) -> Model:
942
+ tree = ET.parse(file_path)
943
+
944
+ el = tree.getroot().find("./namespace", GIR_NAMESPACES)
945
+ namespace = Namespace(
946
+ el.get("name"), el.get(f"{{{C_NAMESPACE}}}identifier-prefixes"), el
947
+ )
948
+
949
+ def resolve_type(name: str) -> Tuple[str, ET.Element]:
950
+ assert (
951
+ name not in PRIMITIVE_GIR_TYPES
952
+ ), f"unexpectedly asked to resolve primitive type: {name}"
953
+
954
+ tokens = name.split(".", maxsplit=1)
955
+ if len(tokens) == 2:
956
+ ns_name, bare_name = tokens
957
+ if ns_name == namespace.name:
958
+ ns = namespace
959
+ else:
960
+ ns = next(
961
+ (
962
+ dep.namespace
963
+ for dep in dependencies
964
+ if dep.namespace.name == ns_name
965
+ ),
966
+ None,
967
+ )
968
+ if ns is None:
969
+ assert ns is not None, f"unable to resolve namespace {ns_name}"
970
+ else:
971
+ ns = namespace
972
+ bare_name = name
973
+ qualified_name = f"{ns.name}.{bare_name}"
974
+
975
+ element = ns.type_elements.get(bare_name)
976
+ assert element is not None, f"unable to resolve type {bare_name}"
977
+
978
+ return (qualified_name, element)
979
+
980
+ object_types = OrderedDict()
981
+
982
+ for element in namespace.element.findall("./class", GIR_NAMESPACES):
983
+ name = element.get("name")
984
+ c_type = element.get(f"{{{C_NAMESPACE}}}type")
985
+ get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
986
+ type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct")
987
+ if type_struct is not None:
988
+ type_struct = namespace.identifier_prefixes + type_struct
989
+ else:
990
+ type_struct = c_type + "Class"
991
+ parent = element.get("parent")
992
+ if parent is not None:
993
+ parent, _ = resolve_type(parent)
994
+ constructors = element.findall(".//constructor", GIR_NAMESPACES)
995
+ methods = element.findall(".//method", GIR_NAMESPACES)
996
+ properties = element.findall(".//property", GIR_NAMESPACES)
997
+ signals = element.findall(".//glib:signal", GIR_NAMESPACES)
998
+ implements = [
999
+ e.get("name") for e in element.findall(".//implements", GIR_NAMESPACES)
1000
+ ]
1001
+
1002
+ object_types[name] = ClassObjectType(
1003
+ name,
1004
+ c_type,
1005
+ get_type,
1006
+ type_struct,
1007
+ parent,
1008
+ constructors,
1009
+ methods,
1010
+ properties,
1011
+ signals,
1012
+ resolve_type,
1013
+ None,
1014
+ implements,
1015
+ )
1016
+
1017
+ for element in namespace.element.findall("./interface", GIR_NAMESPACES):
1018
+ name = element.get("name")
1019
+ c_type = element.get(f"{{{C_NAMESPACE}}}type")
1020
+ get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
1021
+ type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct")
1022
+ if type_struct is not None:
1023
+ type_struct = namespace.identifier_prefixes + type_struct
1024
+ else:
1025
+ type_struct = c_type + "Iface"
1026
+ prereq = element.find(".//prerequisite", GIR_NAMESPACES)
1027
+ parent = prereq.get("name") if prereq is not None else None
1028
+ if parent is not None:
1029
+ parent, _ = resolve_type(parent)
1030
+ constructors = []
1031
+ methods = element.findall(".//method", GIR_NAMESPACES)
1032
+ properties = element.findall(".//property", GIR_NAMESPACES)
1033
+ signals = element.findall(".//glib:signal", GIR_NAMESPACES)
1034
+
1035
+ object_types[name] = InterfaceObjectType(
1036
+ name,
1037
+ c_type,
1038
+ get_type,
1039
+ type_struct,
1040
+ parent,
1041
+ constructors,
1042
+ methods,
1043
+ properties,
1044
+ signals,
1045
+ resolve_type,
1046
+ None,
1047
+ )
1048
+
1049
+ enumerations = OrderedDict()
1050
+
1051
+ for element in namespace.element.findall("./enumeration", GIR_NAMESPACES):
1052
+ if element.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None:
1053
+ continue
1054
+ enum_name = element.get("name")
1055
+ enum_c_type = element.get(f"{{{C_NAMESPACE}}}type")
1056
+ get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
1057
+ members = element.findall(".//member", GIR_NAMESPACES)
1058
+ enumerations[enum_name] = Enumeration(
1059
+ enum_name, enum_c_type, get_type, members, None
1060
+ )
1061
+
1062
+ model = Model(namespace, object_types, enumerations)
1063
+
1064
+ for t in object_types.values():
1065
+ t.model = model
1066
+ for t in enumerations.values():
1067
+ t.model = model
1068
+
1069
+ return model
1070
+
1071
+
1072
+ def extract_callable_details(
1073
+ element: ET.Element,
1074
+ result_element: ET.Element,
1075
+ object_type: ObjectType,
1076
+ resolve_type: ResolveTypeCallback,
1077
+ ) -> Tuple[str, Optional[str], List[Parameter], bool, bool, ET.Element]:
1078
+ c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier")
1079
+
1080
+ parameters = element.findall("./parameters/parameter", GIR_NAMESPACES)
1081
+ full_param_list = extract_parameters(
1082
+ parameters,
1083
+ nullable_implies_optional=True,
1084
+ object_type=object_type,
1085
+ resolve_type=resolve_type,
1086
+ )
1087
+ param_list = list(all_regular_parameters(full_param_list))
1088
+ has_closure_param = any((param.get("closure") == "1" for param in parameters))
1089
+
1090
+ is_async = any(
1091
+ param.type.name == "Gio.AsyncReadyCallback" for param in full_param_list
1092
+ )
1093
+ if not is_async:
1094
+ result_element = element
1095
+
1096
+ finish_c_identifier = (
1097
+ result_element.get(f"{{{C_NAMESPACE}}}identifier") if is_async else None
1098
+ )
1099
+
1100
+ throws = result_element.get("throws") == "1"
1101
+
1102
+ return (
1103
+ c_identifier,
1104
+ finish_c_identifier,
1105
+ param_list,
1106
+ has_closure_param,
1107
+ throws,
1108
+ result_element,
1109
+ )
1110
+
1111
+
1112
+ def extract_parameters(
1113
+ parameter_elements: List[ET.Element],
1114
+ nullable_implies_optional: bool,
1115
+ object_type: ObjectType,
1116
+ resolve_type: ResolveTypeCallback,
1117
+ ) -> List[Parameter]:
1118
+ entries = []
1119
+ for param in parameter_elements:
1120
+ nullable = param.get("nullable") == "1"
1121
+ entries.append((param, nullable))
1122
+
1123
+ last_required_index = None
1124
+ for i, (param, nullable) in enumerate(entries):
1125
+ optional = nullable and nullable_implies_optional
1126
+ if not optional:
1127
+ last_required_index = i
1128
+
1129
+ param_list = []
1130
+ for i, (param, nullable) in enumerate(entries):
1131
+ name = param.get("name")
1132
+ type = extract_type_from_entity(param, resolve_type)
1133
+
1134
+ if last_required_index is None or i > last_required_index:
1135
+ optional = nullable and nullable_implies_optional
1136
+ else:
1137
+ optional = False
1138
+
1139
+ ownership_val = param.get("transfer-ownership")
1140
+ transfer_ownership = (
1141
+ TransferOwnership[ownership_val]
1142
+ if ownership_val is not None
1143
+ else TransferOwnership.none
1144
+ )
1145
+
1146
+ raw_direction = param.get("direction")
1147
+ direction = (
1148
+ Direction(raw_direction) if raw_direction is not None else Direction.IN
1149
+ )
1150
+
1151
+ param_list.append(
1152
+ Parameter(
1153
+ name,
1154
+ type,
1155
+ optional,
1156
+ nullable,
1157
+ transfer_ownership,
1158
+ direction,
1159
+ object_type,
1160
+ )
1161
+ )
1162
+ return param_list
1163
+
1164
+
1165
+ def all_regular_parameters(parameters: List[Parameter]) -> Iterator[Parameter]:
1166
+ callback_index = None
1167
+ for i, param in enumerate(parameters):
1168
+ if param.type.name == "Gio.AsyncReadyCallback":
1169
+ callback_index = i
1170
+ continue
1171
+
1172
+ if callback_index is not None and i == callback_index + 1:
1173
+ continue
1174
+
1175
+ yield param
1176
+
1177
+
1178
+ def extract_type_from_entity(
1179
+ parent_element: ET.Element, resolve_type: ResolveTypeCallback
1180
+ ) -> Optional[Type]:
1181
+ child = parent_element.find("type", GIR_NAMESPACES)
1182
+ if child is None:
1183
+ child = parent_element.find("array", GIR_NAMESPACES)
1184
+ assert child is not None
1185
+ element_type = extract_type_from_entity(child, resolve_type)
1186
+ if element_type.name == "utf8":
1187
+ return Type(
1188
+ "utf8[]",
1189
+ "strv",
1190
+ "gchar **",
1191
+ "NULL",
1192
+ "g_strdupv",
1193
+ "g_strfreev",
1194
+ )
1195
+ elif element_type.name == "gchar":
1196
+ return Type("char[]", "chararray", "gchar *", "NULL", "NULL", "NULL")
1197
+ elif element_type.name == "GObject.Value":
1198
+ return Type("Value[]", "valuearray", "GValue *", "NULL", "NULL", "NULL")
1199
+ else:
1200
+ assert (
1201
+ element_type.name == "guint8"
1202
+ ), f"unsupported array type: {element_type.name}"
1203
+ return Type("uint8[]", "bytearray", "guint8 *", "NULL", "NULL", "NULL")
1204
+
1205
+ return parse_type(child, resolve_type)
1206
+
1207
+
1208
+ def parse_type(
1209
+ element: ET.Element, resolve_type: ResolveTypeCallback
1210
+ ) -> Optional[Type]:
1211
+ name = element.get("name")
1212
+ assert name is not None
1213
+ if name == "none":
1214
+ return None
1215
+
1216
+ is_primitive = name in PRIMITIVE_GIR_TYPES
1217
+ c_type = element.get(f"{{{C_NAMESPACE}}}type")
1218
+
1219
+ core_tag = None
1220
+ if is_primitive:
1221
+ type_element = element
1222
+ if c_type is None:
1223
+ c_type = name
1224
+ else:
1225
+ name, type_element = resolve_type(name)
1226
+ if type_element.tag.startswith(CORE_TAG_PREFIX):
1227
+ core_tag = type_element.tag[len(CORE_TAG_PREFIX) :]
1228
+ c_type = type_element.get(f"{{{C_NAMESPACE}}}type")
1229
+ if core_tag in {"class", "interface", "record"}:
1230
+ c_type += "*"
1231
+
1232
+ nick = type_nick_from_name(name, element, resolve_type)
1233
+ c = c_type.replace("*", " *")
1234
+
1235
+ default_value = "NULL" if "*" in c else None
1236
+
1237
+ if name == "utf8":
1238
+ copy_func = "g_strdup"
1239
+ destroy_func = "g_free"
1240
+ elif name == "utf8[]":
1241
+ copy_func = "g_strdupv"
1242
+ destroy_func = "g_strfreev"
1243
+ elif name == "GLib.HashTable":
1244
+ copy_func = "g_hash_table_ref"
1245
+ destroy_func = "g_hash_table_unref"
1246
+ elif name == "GLib.Quark":
1247
+ copy_func = None
1248
+ destroy_func = None
1249
+ elif name == "GObject.Value":
1250
+ copy_func = "g_value_copy"
1251
+ destroy_func = "g_value_reset"
1252
+ elif name == "GObject.Closure":
1253
+ copy_func = "g_closure_ref"
1254
+ destroy_func = "g_closure_unref"
1255
+ elif core_tag in {"class", "interface"}:
1256
+ copy_func = "g_object_ref"
1257
+ destroy_func = "g_object_unref"
1258
+ elif is_primitive or core_tag in {"bitfield", "callback", "enumeration"}:
1259
+ copy_func = None
1260
+ destroy_func = None
1261
+ else:
1262
+ copy_func = type_element.get("copy-function")
1263
+ destroy_func = type_element.get("free-function")
1264
+ assert (
1265
+ destroy_func is not None
1266
+ ), f"unable to resolve destroy function for {name}, core_tag={core_tag}"
1267
+
1268
+ return Type(name, nick, c, default_value, copy_func, destroy_func)
1269
+
1270
+
1271
+ def type_nick_from_name(
1272
+ name: str, element: ET.Element, resolve_type: ResolveTypeCallback
1273
+ ) -> str:
1274
+ if name == "GLib.PollFD":
1275
+ return "pollfd"
1276
+
1277
+ tokens = name.split(".", maxsplit=1)
1278
+ if len(tokens) == 1:
1279
+ result = tokens[0]
1280
+ if result.startswith("g"):
1281
+ result = result[1:]
1282
+ else:
1283
+ result = to_snake_case(tokens[1])
1284
+
1285
+ if result == "hash_table":
1286
+ key_type = parse_type(element[0], resolve_type)
1287
+ value_type = parse_type(element[1], resolve_type)
1288
+ assert (
1289
+ key_type.name == "utf8" and value_type.name == "GLib.Variant"
1290
+ ), "only GHashTable<string, Variant> is supported for now"
1291
+ result = "vardict"
1292
+
1293
+ return result
1294
+
1295
+
1296
+ def js_type_from_gir(name: str) -> str:
1297
+ if name == "gboolean":
1298
+ return "boolean"
1299
+ if name in NUMERIC_GIR_TYPES:
1300
+ return "number"
1301
+ if name == "utf8":
1302
+ return "string"
1303
+ if name == "utf8[]":
1304
+ return "string[]"
1305
+ if name == "GLib.Bytes":
1306
+ return "Buffer"
1307
+ if name == "GLib.HashTable":
1308
+ return "VariantDict"
1309
+ if name == "GLib.Variant":
1310
+ return "any"
1311
+ if name in {"Gio.File", "Gio.TlsCertificate"}:
1312
+ return "string"
1313
+ if name.startswith("Frida.") and name.endswith("List"):
1314
+ return name[6:-4] + "[]"
1315
+ return name.split(".")[-1]
1316
+
1317
+
1318
+ def to_snake_case(name: str) -> str:
1319
+ result = []
1320
+ i = 0
1321
+ n = len(name)
1322
+ while i < n:
1323
+ if name[i].isupper():
1324
+ if i > 0:
1325
+ result.append("_")
1326
+ start = i
1327
+ if i + 1 < n and name[i + 1].islower():
1328
+ while i + 1 < n and name[i + 1].islower():
1329
+ i += 1
1330
+ else:
1331
+ while i + 1 < n and name[i + 1].isupper():
1332
+ i += 1
1333
+ if i + 1 < n:
1334
+ i -= 1
1335
+ result.append(name[start : i + 1].lower())
1336
+ else:
1337
+ result.append(name[i])
1338
+ i += 1
1339
+ return "".join(result)
1340
+
1341
+
1342
+ def to_pascal_case(name: str) -> str:
1343
+ return "".join(word.capitalize() for word in name.split("_"))
1344
+
1345
+
1346
+ def to_camel_case(name: str) -> str:
1347
+ words = name.split("_")
1348
+ return words[0] + "".join(word.capitalize() for word in words[1:])
1349
+
1350
+
1351
+ def to_macro_case(identifier: str) -> str:
1352
+ result = []
1353
+ for i, char in enumerate(identifier):
1354
+ if char.isupper() and i != 0:
1355
+ result.append("_")
1356
+ result.append(char)
1357
+ return "".join(result).upper()