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.
@@ -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