Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : * GObject introspection: Typelib creation
3 : : *
4 : : * Copyright (C) 2005 Matthias Clasen
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General Public
19 : : * License along with this library; if not, write to the
20 : : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 : : * Boston, MA 02111-1307, USA.
22 : : */
23 : :
24 : : #include "config.h"
25 : :
26 : : #include "girmodule-private.h"
27 : :
28 : : #include "girnode-private.h"
29 : : #include "gitypelib-internal.h"
30 : :
31 : : #include <stdio.h>
32 : : #include <string.h>
33 : : #include <stdlib.h>
34 : :
35 : : #define ALIGN_VALUE(this, boundary) \
36 : : (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
37 : :
38 : : #define NUM_SECTIONS 2
39 : :
40 : : /*< private >
41 : : * gi_ir_module_new:
42 : : * @name:
43 : : * @version:
44 : : * @shared_library: (nullable):
45 : : * @c_prefix:
46 : : *
47 : : * Since: 2.80
48 : : */
49 : : GIIrModule *
50 : 76 : gi_ir_module_new (const char *name,
51 : : const char *version,
52 : : const char *shared_library,
53 : : const char *c_prefix)
54 : : {
55 : : GIIrModule *module;
56 : :
57 : 76 : module = g_slice_new0 (GIIrModule);
58 : :
59 : 76 : module->name = g_strdup (name);
60 : 76 : module->version = g_strdup (version);
61 : 76 : module->shared_library = g_strdup (shared_library);
62 : 76 : module->c_prefix = g_strdup (c_prefix);
63 : 76 : module->dependencies = NULL;
64 : 76 : module->entries = NULL;
65 : :
66 : 76 : module->include_modules = NULL;
67 : 76 : module->aliases = NULL;
68 : :
69 : 76 : return module;
70 : : }
71 : :
72 : : void
73 : 76 : gi_ir_module_free (GIIrModule *module)
74 : : {
75 : : GList *e;
76 : :
77 : 76 : g_free (module->name);
78 : 76 : g_free (module->version);
79 : 76 : g_free (module->shared_library);
80 : 76 : g_free (module->c_prefix);
81 : :
82 : 38280 : for (e = module->entries; e; e = e->next)
83 : 38204 : gi_ir_node_free ((GIIrNode *)e->data);
84 : :
85 : 76 : g_list_free (module->entries);
86 : 76 : g_clear_pointer (&module->dependencies, g_ptr_array_unref);
87 : :
88 : 76 : g_list_free (module->include_modules);
89 : :
90 : 76 : g_hash_table_destroy (module->aliases);
91 : 76 : g_hash_table_destroy (module->pointer_structures);
92 : 76 : g_hash_table_destroy (module->disguised_structures);
93 : :
94 : 76 : g_slice_free (GIIrModule, module);
95 : 76 : }
96 : :
97 : : /**
98 : : * gi_ir_module_fatal:
99 : : * @build: Current build
100 : : * @line: Origin line number, or 0 if unknown
101 : : * @msg: printf-format string
102 : : * @args: Remaining arguments
103 : : *
104 : : * Report a fatal error, then exit.
105 : : *
106 : : * Since: 2.80
107 : : */
108 : : void
109 : 0 : gi_ir_module_fatal (GIIrTypelibBuild *build,
110 : : unsigned int line,
111 : : const char *msg,
112 : : ...)
113 : : {
114 : : GString *context;
115 : : char *formatted;
116 : : GList *link;
117 : :
118 : : va_list args;
119 : :
120 : 0 : va_start (args, msg);
121 : :
122 : 0 : formatted = g_strdup_vprintf (msg, args);
123 : :
124 : 0 : context = g_string_new ("");
125 : 0 : if (line > 0)
126 : 0 : g_string_append_printf (context, "%u: ", line);
127 : 0 : if (build->stack)
128 : 0 : g_string_append (context, "In ");
129 : 0 : for (link = g_list_last (build->stack); link; link = link->prev)
130 : : {
131 : 0 : GIIrNode *node = link->data;
132 : 0 : const char *name = node->name;
133 : 0 : if (name)
134 : 0 : g_string_append (context, name);
135 : 0 : if (link->prev)
136 : 0 : g_string_append (context, ".");
137 : 0 : }
138 : 0 : if (build->stack)
139 : 0 : g_string_append (context, ": ");
140 : :
141 : 0 : g_printerr ("%s-%s.gir:%serror: %s\n", build->module->name,
142 : 0 : build->module->version,
143 : 0 : context->str, formatted);
144 : 0 : g_string_free (context, TRUE);
145 : :
146 : 0 : exit (1);
147 : :
148 : : va_end (args);
149 : : }
150 : :
151 : : static void
152 : 1042 : add_alias_foreach (gpointer key,
153 : : gpointer value,
154 : : gpointer data)
155 : : {
156 : 1042 : GIIrModule *module = data;
157 : :
158 : 1042 : g_hash_table_replace (module->aliases, g_strdup (key), g_strdup (value));
159 : 1042 : }
160 : :
161 : : static void
162 : 76 : add_pointer_structure_foreach (gpointer key,
163 : : gpointer value,
164 : : gpointer data)
165 : : {
166 : 76 : GIIrModule *module = data;
167 : :
168 : 76 : g_hash_table_replace (module->pointer_structures, g_strdup (key), value);
169 : 76 : }
170 : :
171 : : static void
172 : 1552 : add_disguised_structure_foreach (gpointer key,
173 : : gpointer value,
174 : : gpointer data)
175 : : {
176 : 1552 : GIIrModule *module = data;
177 : :
178 : 1552 : g_hash_table_replace (module->disguised_structures, g_strdup (key), value);
179 : 1552 : }
180 : :
181 : : void
182 : 76 : gi_ir_module_add_include_module (GIIrModule *module,
183 : : GIIrModule *include_module)
184 : : {
185 : 114 : module->include_modules = g_list_prepend (module->include_modules,
186 : 38 : include_module);
187 : :
188 : 114 : g_hash_table_foreach (include_module->aliases,
189 : : add_alias_foreach,
190 : 38 : module);
191 : :
192 : 114 : g_hash_table_foreach (include_module->pointer_structures,
193 : : add_pointer_structure_foreach,
194 : 38 : module);
195 : 114 : g_hash_table_foreach (include_module->disguised_structures,
196 : : add_disguised_structure_foreach,
197 : 38 : module);
198 : 76 : }
199 : :
200 : : struct AttributeWriteData
201 : : {
202 : : unsigned int count;
203 : : uint8_t *databuf;
204 : : GIIrNode *node;
205 : : GHashTable *strings;
206 : : uint32_t *offset;
207 : : uint32_t *offset2;
208 : : };
209 : :
210 : : static void
211 : 7575 : write_attribute (gpointer key, gpointer value, gpointer datap)
212 : : {
213 : 7575 : struct AttributeWriteData *data = datap;
214 : 7575 : uint32_t old_offset = *(data->offset);
215 : 7575 : AttributeBlob *blob = (AttributeBlob*)&(data->databuf[old_offset]);
216 : :
217 : 7575 : *(data->offset) += sizeof (AttributeBlob);
218 : :
219 : 7575 : blob->offset = data->node->offset;
220 : 7575 : blob->name = gi_ir_write_string ((const char*) key, data->strings, data->databuf, data->offset2);
221 : 7575 : blob->value = gi_ir_write_string ((const char*) value, data->strings, data->databuf, data->offset2);
222 : :
223 : 7575 : data->count++;
224 : 7575 : }
225 : :
226 : : static unsigned
227 : 224578 : write_attributes (GIIrModule *module,
228 : : GIIrNode *node,
229 : : GHashTable *strings,
230 : : uint8_t *data,
231 : : uint32_t *offset,
232 : : uint32_t *offset2)
233 : : {
234 : : struct AttributeWriteData wdata;
235 : 224578 : wdata.count = 0;
236 : 224578 : wdata.databuf = data;
237 : 224578 : wdata.node = node;
238 : 224578 : wdata.offset = offset;
239 : 224578 : wdata.offset2 = offset2;
240 : 224578 : wdata.strings = strings;
241 : :
242 : 224578 : g_hash_table_foreach (node->attributes, write_attribute, &wdata);
243 : :
244 : 224578 : return wdata.count;
245 : : }
246 : :
247 : : static int
248 : 1693767 : node_cmp_offset_func (const void *a,
249 : : const void *b)
250 : : {
251 : 1693767 : const GIIrNode *na = a;
252 : 1693767 : const GIIrNode *nb = b;
253 : 1693767 : return (int) na->offset - (int) nb->offset;
254 : : }
255 : :
256 : : static void
257 : 26 : alloc_section (uint8_t *data, SectionType section_id, uint32_t offset)
258 : : {
259 : : int i;
260 : 26 : Header *header = (Header*)data;
261 : 26 : Section *section_data = (Section*)&data[header->sections];
262 : :
263 : 26 : g_assert (section_id != GI_SECTION_END);
264 : :
265 : 26 : for (i = 0; i < NUM_SECTIONS; i++)
266 : : {
267 : 26 : if (section_data->id == GI_SECTION_END)
268 : : {
269 : 26 : section_data->id = section_id;
270 : 26 : section_data->offset = offset;
271 : 26 : return;
272 : : }
273 : 0 : section_data++;
274 : 0 : }
275 : : g_assert_not_reached ();
276 : 13 : }
277 : :
278 : : static uint8_t *
279 : 26 : add_directory_index_section (uint8_t *data, GIIrModule *module, uint32_t *offset2)
280 : : {
281 : : DirEntry *entry;
282 : 26 : Header *header = (Header*)data;
283 : : GITypelibHashBuilder *dirindex_builder;
284 : : uint16_t n_interfaces;
285 : : uint16_t required_size;
286 : : uint32_t new_offset;
287 : :
288 : 26 : dirindex_builder = gi_typelib_hash_builder_new ();
289 : :
290 : 26 : n_interfaces = ((Header *)data)->n_local_entries;
291 : :
292 : 12337 : for (uint16_t i = 0; i < n_interfaces; i++)
293 : : {
294 : : const char *str;
295 : 12311 : entry = (DirEntry *)&data[header->directory + (i * header->entry_blob_size)];
296 : 12311 : str = (const char *) (&data[entry->name]);
297 : 12311 : gi_typelib_hash_builder_add_string (dirindex_builder, str, i);
298 : 6137 : }
299 : :
300 : 26 : if (!gi_typelib_hash_builder_prepare (dirindex_builder))
301 : : {
302 : : /* This happens if CMPH couldn't create a perfect hash. So
303 : : * we just punt and leave no directory index section.
304 : : */
305 : 0 : gi_typelib_hash_builder_destroy (dirindex_builder);
306 : 0 : return data;
307 : : }
308 : :
309 : 26 : alloc_section (data, GI_SECTION_DIRECTORY_INDEX, *offset2);
310 : :
311 : 26 : required_size = gi_typelib_hash_builder_get_buffer_size (dirindex_builder);
312 : 26 : required_size = ALIGN_VALUE (required_size, 4);
313 : :
314 : 26 : new_offset = *offset2 + required_size;
315 : :
316 : 26 : data = g_realloc (data, new_offset);
317 : :
318 : 26 : gi_typelib_hash_builder_pack (dirindex_builder, ((uint8_t*)data) + *offset2, required_size);
319 : :
320 : 26 : *offset2 = new_offset;
321 : :
322 : 26 : gi_typelib_hash_builder_destroy (dirindex_builder);
323 : 26 : return data;
324 : 13 : }
325 : :
326 : : GITypelib *
327 : 26 : gi_ir_module_build_typelib (GIIrModule *module)
328 : : {
329 : 26 : GError *error = NULL;
330 : 26 : GBytes *bytes = NULL;
331 : : GITypelib *typelib;
332 : : size_t length;
333 : : size_t i;
334 : : GList *e;
335 : : Header *header;
336 : : DirEntry *entry;
337 : : uint32_t header_size;
338 : : uint32_t dir_size;
339 : : uint32_t n_entries;
340 : : uint32_t n_local_entries;
341 : : uint32_t size, offset, offset2, old_offset;
342 : : GHashTable *strings;
343 : : GHashTable *types;
344 : : GList *nodes_with_attributes;
345 : : char *dependencies;
346 : : uint8_t *data;
347 : : Section *section;
348 : :
349 : 26 : header_size = ALIGN_VALUE (sizeof (Header), 4);
350 : 26 : n_local_entries = g_list_length (module->entries);
351 : :
352 : : /* Serialize dependencies into one string; this is convenient
353 : : * and not a major change to the typelib format. */
354 : 39 : if (module->dependencies->len)
355 : : {
356 : 20 : GString *dependencies_str = g_string_new (NULL);
357 : 64 : for (guint i = module->dependencies->len; i > 0; --i)
358 : : {
359 : 44 : const char *dependency = g_ptr_array_index (module->dependencies, i-1);
360 : 44 : if (!strcmp (dependency, module->name))
361 : 0 : continue;
362 : 22 : g_string_append (dependencies_str, dependency);
363 : 44 : if (i > 1)
364 : 12 : g_string_append_c (dependencies_str, '|');
365 : 22 : }
366 : 20 : dependencies = g_string_free (dependencies_str, FALSE);
367 : 20 : if (dependencies && !dependencies[0])
368 : : {
369 : 0 : g_free (dependencies);
370 : 0 : dependencies = NULL;
371 : 0 : }
372 : 10 : }
373 : : else
374 : : {
375 : 6 : dependencies = NULL;
376 : : }
377 : :
378 : 13 : restart:
379 : 43 : gi_ir_node_init_stats ();
380 : 43 : strings = g_hash_table_new (g_str_hash, g_str_equal);
381 : 43 : types = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
382 : 43 : nodes_with_attributes = NULL;
383 : 43 : n_entries = g_list_length (module->entries);
384 : :
385 : 64 : g_message ("%d entries (%d local), %u dependencies", n_entries, n_local_entries,
386 : 21 : module->dependencies ? module->dependencies->len : 0);
387 : :
388 : 43 : dir_size = n_entries * sizeof (DirEntry);
389 : 43 : size = header_size + dir_size;
390 : :
391 : 43 : size += ALIGN_VALUE (strlen (module->name) + 1, 4);
392 : :
393 : 19077 : for (e = module->entries; e; e = e->next)
394 : : {
395 : 19034 : GIIrNode *node = e->data;
396 : :
397 : 19034 : size += gi_ir_node_get_full_size (node);
398 : :
399 : : /* Also reset the offset here */
400 : 19034 : node->offset = 0;
401 : 9463 : }
402 : :
403 : : /* Adjust size for strings allocated in header below specially */
404 : 43 : size += ALIGN_VALUE (strlen (module->name) + 1, 4);
405 : 43 : if (module->shared_library)
406 : 43 : size += ALIGN_VALUE (strlen (module->shared_library) + 1, 4);
407 : 43 : if (dependencies != NULL)
408 : 37 : size += ALIGN_VALUE (strlen (dependencies) + 1, 4);
409 : 43 : if (module->c_prefix != NULL)
410 : 43 : size += ALIGN_VALUE (strlen (module->c_prefix) + 1, 4);
411 : :
412 : 43 : size += sizeof (Section) * NUM_SECTIONS;
413 : :
414 : 43 : g_message ("allocating %d bytes (%d header, %d directory, %d entries)",
415 : 21 : size, header_size, dir_size, size - header_size - dir_size);
416 : :
417 : 43 : data = g_malloc0 (size);
418 : :
419 : : /* fill in header */
420 : 43 : header = (Header *)data;
421 : 43 : memcpy (header, GI_IR_MAGIC, 16);
422 : 43 : header->major_version = 4;
423 : 43 : header->minor_version = 0;
424 : 43 : header->reserved = 0;
425 : 43 : header->n_entries = n_entries;
426 : 43 : header->n_local_entries = n_local_entries;
427 : 43 : header->n_attributes = 0;
428 : 43 : header->attributes = 0; /* filled in later */
429 : : /* NOTE: When writing strings to the typelib here, you should also update
430 : : * the size calculations above.
431 : : */
432 : 43 : if (dependencies != NULL)
433 : 37 : header->dependencies = gi_ir_write_string (dependencies, strings, data, &header_size);
434 : : else
435 : 6 : header->dependencies = 0;
436 : 43 : header->size = 0; /* filled in later */
437 : 43 : header->namespace = gi_ir_write_string (module->name, strings, data, &header_size);
438 : 43 : header->nsversion = gi_ir_write_string (module->version, strings, data, &header_size);
439 : 65 : header->shared_library = (module->shared_library?
440 : 43 : gi_ir_write_string (module->shared_library, strings, data, &header_size)
441 : 22 : : 0);
442 : 43 : if (module->c_prefix != NULL)
443 : 43 : header->c_prefix = gi_ir_write_string (module->c_prefix, strings, data, &header_size);
444 : : else
445 : 0 : header->c_prefix = 0;
446 : 43 : header->entry_blob_size = sizeof (DirEntry);
447 : 43 : header->function_blob_size = sizeof (FunctionBlob);
448 : 43 : header->callback_blob_size = sizeof (CallbackBlob);
449 : 43 : header->signal_blob_size = sizeof (SignalBlob);
450 : 43 : header->vfunc_blob_size = sizeof (VFuncBlob);
451 : 43 : header->arg_blob_size = sizeof (ArgBlob);
452 : 43 : header->property_blob_size = sizeof (PropertyBlob);
453 : 43 : header->field_blob_size = sizeof (FieldBlob);
454 : 43 : header->value_blob_size = sizeof (ValueBlob);
455 : 43 : header->constant_blob_size = sizeof (ConstantBlob);
456 : 43 : header->error_domain_blob_size = 16; /* No longer used */
457 : 43 : header->attribute_blob_size = sizeof (AttributeBlob);
458 : 43 : header->signature_blob_size = sizeof (SignatureBlob);
459 : 43 : header->enum_blob_size = sizeof (EnumBlob);
460 : 43 : header->struct_blob_size = sizeof (StructBlob);
461 : 43 : header->object_blob_size = sizeof(ObjectBlob);
462 : 43 : header->interface_blob_size = sizeof (InterfaceBlob);
463 : 43 : header->union_blob_size = sizeof (UnionBlob);
464 : :
465 : 43 : offset2 = ALIGN_VALUE (header_size, 4);
466 : 43 : header->sections = offset2;
467 : :
468 : : /* Initialize all the sections to _END/0; we fill them in later using
469 : : * alloc_section(). (Right now there's just the directory index
470 : : * though, note)
471 : : */
472 : 129 : for (i = 0; i < NUM_SECTIONS; i++)
473 : : {
474 : 86 : section = (Section*) &data[offset2];
475 : 86 : section->id = GI_SECTION_END;
476 : 86 : section->offset = 0;
477 : 86 : offset2 += sizeof(Section);
478 : 42 : }
479 : 43 : header->directory = offset2;
480 : :
481 : : /* fill in directory and content */
482 : 43 : entry = (DirEntry *)&data[header->directory];
483 : :
484 : 43 : offset2 += dir_size;
485 : :
486 : 19077 : for (e = module->entries, i = 0; e; e = e->next, i++)
487 : : {
488 : 19051 : GIIrNode *node = e->data;
489 : :
490 : 19051 : if (strchr (node->name, '.'))
491 : : {
492 : 0 : g_error ("Names may not contain '.'");
493 : 0 : }
494 : :
495 : : /* we picked up implicit xref nodes, start over */
496 : 19051 : if (i == n_entries)
497 : : {
498 : : GList *link;
499 : 17 : g_message ("Found implicit cross references, starting over");
500 : :
501 : 17 : g_hash_table_destroy (strings);
502 : 17 : g_hash_table_destroy (types);
503 : :
504 : : /* Reset the cached offsets */
505 : 153981 : for (link = nodes_with_attributes; link; link = link->next)
506 : 153964 : ((GIIrNode *) link->data)->offset = 0;
507 : :
508 : 17 : g_list_free (nodes_with_attributes);
509 : 17 : strings = NULL;
510 : :
511 : 17 : g_free (data);
512 : 17 : data = NULL;
513 : :
514 : 17 : goto restart;
515 : : }
516 : :
517 : 19034 : offset = offset2;
518 : :
519 : 19034 : if (node->type == GI_IR_NODE_XREF)
520 : : {
521 : 275 : const char *namespace = ((GIIrNodeXRef*)node)->namespace;
522 : :
523 : 275 : entry->blob_type = 0;
524 : 275 : entry->local = FALSE;
525 : 275 : entry->offset = gi_ir_write_string (namespace, strings, data, &offset2);
526 : 275 : entry->name = gi_ir_write_string (node->name, strings, data, &offset2);
527 : 129 : }
528 : : else
529 : : {
530 : 18759 : GIIrTypelibBuild build = {0};
531 : 18759 : old_offset = offset;
532 : 18759 : offset2 = offset + gi_ir_node_get_size (node);
533 : :
534 : 18759 : entry->blob_type = node->type;
535 : 18759 : entry->local = TRUE;
536 : 18759 : entry->offset = offset;
537 : 18759 : entry->name = gi_ir_write_string (node->name, strings, data, &offset2);
538 : :
539 : 18759 : build.module = module;
540 : 18759 : build.strings = strings;
541 : 18759 : build.types = types;
542 : 18759 : build.nodes_with_attributes = nodes_with_attributes;
543 : 18759 : build.n_attributes = header->n_attributes;
544 : 18759 : build.data = data;
545 : 18759 : gi_ir_node_build_typelib (node, NULL, &build, &offset, &offset2, NULL);
546 : 18759 : g_clear_list (&build.stack, NULL);
547 : :
548 : 18759 : nodes_with_attributes = build.nodes_with_attributes;
549 : 18759 : header->n_attributes = build.n_attributes;
550 : :
551 : 18759 : if (offset2 > old_offset + gi_ir_node_get_full_size (node))
552 : 0 : g_error ("left a hole of %d bytes", offset2 - old_offset - gi_ir_node_get_full_size (node));
553 : : }
554 : :
555 : 19034 : entry++;
556 : 9463 : }
557 : :
558 : : /* GIBaseInfo expects the AttributeBlob array to be sorted on the field (offset) */
559 : 26 : nodes_with_attributes = g_list_sort (nodes_with_attributes, node_cmp_offset_func);
560 : :
561 : 26 : g_message ("header: %d entries, %d attributes", header->n_entries, header->n_attributes);
562 : :
563 : 26 : gi_ir_node_dump_stats ();
564 : :
565 : : /* Write attributes after the blobs */
566 : 26 : offset = offset2;
567 : 26 : header->attributes = offset;
568 : 26 : offset2 = offset + header->n_attributes * header->attribute_blob_size;
569 : :
570 : 224604 : for (e = nodes_with_attributes; e; e = e->next)
571 : : {
572 : 224578 : GIIrNode *node = e->data;
573 : 224578 : write_attributes (module, node, strings, data, &offset, &offset2);
574 : 111806 : }
575 : :
576 : 26 : g_message ("reallocating to %d bytes", offset2);
577 : :
578 : 26 : data = g_realloc (data, offset2);
579 : 26 : header = (Header*) data;
580 : :
581 : 26 : data = add_directory_index_section (data, module, &offset2);
582 : 26 : header = (Header *)data;
583 : :
584 : 26 : length = header->size = offset2;
585 : :
586 : 26 : bytes = g_bytes_new_take (g_steal_pointer (&data), length);
587 : 26 : typelib = gi_typelib_new_from_bytes (bytes, &error);
588 : 26 : g_bytes_unref (bytes);
589 : :
590 : 26 : if (!typelib)
591 : : {
592 : 0 : g_error ("error building typelib: %s",
593 : 0 : error->message);
594 : 0 : }
595 : :
596 : 26 : g_hash_table_destroy (strings);
597 : 26 : g_hash_table_destroy (types);
598 : 26 : g_list_free (nodes_with_attributes);
599 : 26 : g_free (dependencies);
600 : :
601 : 26 : return typelib;
602 : : }
603 : :
|