blob: 987ca27e76059b1134b0bd76c2a23fd1036a7d77 [file] [log] [blame]
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * gc.c: Garbage Collection
12 */
13
14#include "vim.h"
15
16#if defined(FEAT_EVAL) || defined(PROTO)
17
18/*
19 * When recursively copying lists and dicts we need to remember which ones we
20 * have done to avoid endless recursiveness. This unique ID is used for that.
21 * The last bit is used for previous_funccal, ignored when comparing.
22 */
23static int current_copyID = 0;
24
25static int free_unref_items(int copyID);
26
27/*
28 * Return the next (unique) copy ID.
29 * Used for serializing nested structures.
30 */
31 int
32get_copyID(void)
33{
34 current_copyID += COPYID_INC;
35 return current_copyID;
36}
37
38/*
39 * Garbage collection for lists and dictionaries.
40 *
41 * We use reference counts to be able to free most items right away when they
42 * are no longer used. But for composite items it's possible that it becomes
43 * unused while the reference count is > 0: When there is a recursive
44 * reference. Example:
45 * :let l = [1, 2, 3]
46 * :let d = {9: l}
47 * :let l[1] = d
48 *
49 * Since this is quite unusual we handle this with garbage collection: every
50 * once in a while find out which lists and dicts are not referenced from any
51 * variable.
52 *
53 * Here is a good reference text about garbage collection (refers to Python
54 * but it applies to all reference-counting mechanisms):
55 * http://python.ca/nas/python/gc/
56 */
57
58/*
59 * Do garbage collection for lists and dicts.
60 * When "testing" is TRUE this is called from test_garbagecollect_now().
61 * Return TRUE if some memory was freed.
62 */
63 int
64garbage_collect(int testing)
65{
66 int copyID;
67 int abort = FALSE;
68 buf_T *buf;
69 win_T *wp;
70 int did_free = FALSE;
71 tabpage_T *tp;
72
73 if (!testing)
74 {
75 // Only do this once.
76 want_garbage_collect = FALSE;
77 may_garbage_collect = FALSE;
78 garbage_collect_at_exit = FALSE;
79 }
80
81 // The execution stack can grow big, limit the size.
82 if (exestack.ga_maxlen - exestack.ga_len > 500)
83 {
84 size_t new_len;
85 char_u *pp;
86 int n;
87
88 // Keep 150% of the current size, with a minimum of the growth size.
89 n = exestack.ga_len / 2;
90 if (n < exestack.ga_growsize)
91 n = exestack.ga_growsize;
92
93 // Don't make it bigger though.
94 if (exestack.ga_len + n < exestack.ga_maxlen)
95 {
96 new_len = (size_t)exestack.ga_itemsize * (exestack.ga_len + n);
97 pp = vim_realloc(exestack.ga_data, new_len);
98 if (pp == NULL)
99 return FAIL;
100 exestack.ga_maxlen = exestack.ga_len + n;
101 exestack.ga_data = pp;
102 }
103 }
104
105 // We advance by two because we add one for items referenced through
106 // previous_funccal.
107 copyID = get_copyID();
108
109 /*
110 * 1. Go through all accessible variables and mark all lists and dicts
111 * with copyID.
112 */
113
114 // Don't free variables in the previous_funccal list unless they are only
115 // referenced through previous_funccal. This must be first, because if
116 // the item is referenced elsewhere the funccal must not be freed.
117 abort = abort || set_ref_in_previous_funccal(copyID);
118
119 // script-local variables
120 abort = abort || garbage_collect_scriptvars(copyID);
121
122 // buffer-local variables
123 FOR_ALL_BUFFERS(buf)
124 abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID,
125 NULL, NULL);
126
127 // window-local variables
128 FOR_ALL_TAB_WINDOWS(tp, wp)
129 abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
130 NULL, NULL);
131 // window-local variables in autocmd windows
132 for (int i = 0; i < AUCMD_WIN_COUNT; ++i)
133 if (aucmd_win[i].auc_win != NULL)
134 abort = abort || set_ref_in_item(
135 &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL);
136#ifdef FEAT_PROP_POPUP
137 FOR_ALL_POPUPWINS(wp)
138 abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
139 NULL, NULL);
140 FOR_ALL_TABPAGES(tp)
141 FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
142 abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
143 NULL, NULL);
144#endif
145
146 // tabpage-local variables
147 FOR_ALL_TABPAGES(tp)
148 abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID,
149 NULL, NULL);
150 // global variables
151 abort = abort || garbage_collect_globvars(copyID);
152
153 // function-local variables
154 abort = abort || set_ref_in_call_stack(copyID);
155
156 // named functions (matters for closures)
157 abort = abort || set_ref_in_functions(copyID);
158
159 // function call arguments, if v:testing is set.
160 abort = abort || set_ref_in_func_args(copyID);
161
162 // funcstacks keep variables for closures
163 abort = abort || set_ref_in_funcstacks(copyID);
164
165 // loopvars keep variables for loop blocks
166 abort = abort || set_ref_in_loopvars(copyID);
167
168 // v: vars
169 abort = abort || garbage_collect_vimvars(copyID);
170
171 // callbacks in buffers
172 abort = abort || set_ref_in_buffers(copyID);
173
174 // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks
175 abort = abort || set_ref_in_insexpand_funcs(copyID);
176
177 // 'operatorfunc' callback
178 abort = abort || set_ref_in_opfunc(copyID);
179
180 // 'tagfunc' callback
181 abort = abort || set_ref_in_tagfunc(copyID);
182
183 // 'imactivatefunc' and 'imstatusfunc' callbacks
184 abort = abort || set_ref_in_im_funcs(copyID);
185
186#ifdef FEAT_LUA
187 abort = abort || set_ref_in_lua(copyID);
188#endif
189
190#ifdef FEAT_PYTHON
191 abort = abort || set_ref_in_python(copyID);
192#endif
193
194#ifdef FEAT_PYTHON3
195 abort = abort || set_ref_in_python3(copyID);
196#endif
197
198#ifdef FEAT_JOB_CHANNEL
199 abort = abort || set_ref_in_channel(copyID);
200 abort = abort || set_ref_in_job(copyID);
201#endif
202#ifdef FEAT_NETBEANS_INTG
203 abort = abort || set_ref_in_nb_channel(copyID);
204#endif
205
206#ifdef FEAT_TIMERS
207 abort = abort || set_ref_in_timer(copyID);
208#endif
209
210#ifdef FEAT_QUICKFIX
211 abort = abort || set_ref_in_quickfix(copyID);
212#endif
213
214#ifdef FEAT_TERMINAL
215 abort = abort || set_ref_in_term(copyID);
216#endif
217
218#ifdef FEAT_PROP_POPUP
219 abort = abort || set_ref_in_popups(copyID);
220#endif
221
222 abort = abort || set_ref_in_classes(copyID);
223
224 if (!abort)
225 {
226 /*
227 * 2. Free lists and dictionaries that are not referenced.
228 */
229 did_free = free_unref_items(copyID);
230
231 /*
232 * 3. Check if any funccal can be freed now.
233 * This may call us back recursively.
234 */
235 free_unref_funccal(copyID, testing);
236 }
237 else if (p_verbose > 0)
238 {
239 verb_msg(_("Not enough memory to set references, garbage collection aborted!"));
240 }
241
242 return did_free;
243}
244
245/*
246 * Free lists, dictionaries, channels and jobs that are no longer referenced.
247 */
248 static int
249free_unref_items(int copyID)
250{
251 int did_free = FALSE;
252
253 // Let all "free" functions know that we are here. This means no
254 // dictionaries, lists, channels or jobs are to be freed, because we will
255 // do that here.
256 in_free_unref_items = TRUE;
257
258 /*
259 * PASS 1: free the contents of the items. We don't free the items
260 * themselves yet, so that it is possible to decrement refcount counters
261 */
262
263 // Go through the list of dicts and free items without this copyID.
264 did_free |= dict_free_nonref(copyID);
265
266 // Go through the list of lists and free items without this copyID.
267 did_free |= list_free_nonref(copyID);
268
269 // Go through the list of objects and free items without this copyID.
270 did_free |= object_free_nonref(copyID);
271
272 // Go through the list of classes and free items without this copyID.
273 did_free |= class_free_nonref(copyID);
274
275#ifdef FEAT_JOB_CHANNEL
276 // Go through the list of jobs and free items without the copyID. This
277 // must happen before doing channels, because jobs refer to channels, but
278 // the reference from the channel to the job isn't tracked.
279 did_free |= free_unused_jobs_contents(copyID, COPYID_MASK);
280
281 // Go through the list of channels and free items without the copyID.
282 did_free |= free_unused_channels_contents(copyID, COPYID_MASK);
283#endif
284
285 /*
286 * PASS 2: free the items themselves.
287 */
288 object_free_items(copyID);
289 dict_free_items(copyID);
290 list_free_items(copyID);
291
292#ifdef FEAT_JOB_CHANNEL
293 // Go through the list of jobs and free items without the copyID. This
294 // must happen before doing channels, because jobs refer to channels, but
295 // the reference from the channel to the job isn't tracked.
296 free_unused_jobs(copyID, COPYID_MASK);
297
298 // Go through the list of channels and free items without the copyID.
299 free_unused_channels(copyID, COPYID_MASK);
300#endif
301
302 in_free_unref_items = FALSE;
303
304 return did_free;
305}
306
307/*
308 * Mark all lists and dicts referenced through hashtab "ht" with "copyID".
309 * "list_stack" is used to add lists to be marked. Can be NULL.
310 *
311 * Returns TRUE if setting references failed somehow.
312 */
313 int
314set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
315{
316 int todo;
317 int abort = FALSE;
318 hashitem_T *hi;
319 hashtab_T *cur_ht;
320 ht_stack_T *ht_stack = NULL;
321 ht_stack_T *tempitem;
322
323 cur_ht = ht;
324 for (;;)
325 {
326 if (!abort)
327 {
328 // Mark each item in the hashtab. If the item contains a hashtab
329 // it is added to ht_stack, if it contains a list it is added to
330 // list_stack.
331 todo = (int)cur_ht->ht_used;
332 FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo)
333 if (!HASHITEM_EMPTY(hi))
334 {
335 --todo;
336 abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID,
337 &ht_stack, list_stack);
338 }
339 }
340
341 if (ht_stack == NULL)
342 break;
343
344 // take an item from the stack
345 cur_ht = ht_stack->ht;
346 tempitem = ht_stack;
347 ht_stack = ht_stack->prev;
348 free(tempitem);
349 }
350
351 return abort;
352}
353
354#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \
355 || defined(PROTO)
356/*
357 * Mark a dict and its items with "copyID".
358 * Returns TRUE if setting references failed somehow.
359 */
360 int
361set_ref_in_dict(dict_T *d, int copyID)
362{
363 if (d != NULL && d->dv_copyID != copyID)
364 {
365 d->dv_copyID = copyID;
366 return set_ref_in_ht(&d->dv_hashtab, copyID, NULL);
367 }
368 return FALSE;
369}
370#endif
371
372/*
373 * Mark a list and its items with "copyID".
374 * Returns TRUE if setting references failed somehow.
375 */
376 int
377set_ref_in_list(list_T *ll, int copyID)
378{
379 if (ll != NULL && ll->lv_copyID != copyID)
380 {
381 ll->lv_copyID = copyID;
382 return set_ref_in_list_items(ll, copyID, NULL);
383 }
384 return FALSE;
385}
386
387/*
388 * Mark all lists and dicts referenced through list "l" with "copyID".
389 * "ht_stack" is used to add hashtabs to be marked. Can be NULL.
390 *
391 * Returns TRUE if setting references failed somehow.
392 */
393 int
394set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack)
395{
396 listitem_T *li;
397 int abort = FALSE;
398 list_T *cur_l;
399 list_stack_T *list_stack = NULL;
400 list_stack_T *tempitem;
401
402 cur_l = l;
403 for (;;)
404 {
405 if (!abort && cur_l->lv_first != &range_list_item)
406 // Mark each item in the list. If the item contains a hashtab
407 // it is added to ht_stack, if it contains a list it is added to
408 // list_stack.
409 for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next)
410 abort = abort || set_ref_in_item(&li->li_tv, copyID,
411 ht_stack, &list_stack);
412 if (list_stack == NULL)
413 break;
414
415 // take an item from the stack
416 cur_l = list_stack->list;
417 tempitem = list_stack;
418 list_stack = list_stack->prev;
419 free(tempitem);
420 }
421
422 return abort;
423}
424
425/*
426 * Mark the partial in callback 'cb' with "copyID".
427 */
428 int
429set_ref_in_callback(callback_T *cb, int copyID)
430{
431 typval_T tv;
432
433 if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL)
434 return FALSE;
435
436 tv.v_type = VAR_PARTIAL;
437 tv.vval.v_partial = cb->cb_partial;
438 return set_ref_in_item(&tv, copyID, NULL, NULL);
439}
440
441/*
442 * Mark the dict "dd" with "copyID".
443 * Also see set_ref_in_item().
444 */
445 static int
446set_ref_in_item_dict(
447 dict_T *dd,
448 int copyID,
449 ht_stack_T **ht_stack,
450 list_stack_T **list_stack)
451{
452 if (dd == NULL || dd->dv_copyID == copyID)
453 return FALSE;
454
455 // Didn't see this dict yet.
456 dd->dv_copyID = copyID;
457 if (ht_stack == NULL)
458 return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
459
460 ht_stack_T *newitem = ALLOC_ONE(ht_stack_T);
461 if (newitem == NULL)
462 return TRUE;
463
464 newitem->ht = &dd->dv_hashtab;
465 newitem->prev = *ht_stack;
466 *ht_stack = newitem;
467
468 return FALSE;
469}
470
471/*
472 * Mark the list "ll" with "copyID".
473 * Also see set_ref_in_item().
474 */
475 static int
476set_ref_in_item_list(
477 list_T *ll,
478 int copyID,
479 ht_stack_T **ht_stack,
480 list_stack_T **list_stack)
481{
482 if (ll == NULL || ll->lv_copyID == copyID)
483 return FALSE;
484
485 // Didn't see this list yet.
486 ll->lv_copyID = copyID;
487 if (list_stack == NULL)
488 return set_ref_in_list_items(ll, copyID, ht_stack);
489
490 list_stack_T *newitem = ALLOC_ONE(list_stack_T);
491 if (newitem == NULL)
492 return TRUE;
493
494 newitem->list = ll;
495 newitem->prev = *list_stack;
496 *list_stack = newitem;
497
498 return FALSE;
499}
500
501/*
502 * Mark the partial "pt" with "copyID".
503 * Also see set_ref_in_item().
504 */
505 static int
506set_ref_in_item_partial(
507 partial_T *pt,
508 int copyID,
509 ht_stack_T **ht_stack,
510 list_stack_T **list_stack)
511{
512 if (pt == NULL || pt->pt_copyID == copyID)
513 return FALSE;
514
515 // Didn't see this partial yet.
516 pt->pt_copyID = copyID;
517
518 int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID);
519
520 if (pt->pt_dict != NULL)
521 {
522 typval_T dtv;
523
524 dtv.v_type = VAR_DICT;
525 dtv.vval.v_dict = pt->pt_dict;
526 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
527 }
528
529 if (pt->pt_obj != NULL)
530 {
531 typval_T objtv;
532
533 objtv.v_type = VAR_OBJECT;
534 objtv.vval.v_object = pt->pt_obj;
535 set_ref_in_item(&objtv, copyID, ht_stack, list_stack);
536 }
537
538 for (int i = 0; i < pt->pt_argc; ++i)
539 abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID,
540 ht_stack, list_stack);
541 // pt_funcstack is handled in set_ref_in_funcstacks()
542 // pt_loopvars is handled in set_ref_in_loopvars()
543
544 return abort;
545}
546
547#ifdef FEAT_JOB_CHANNEL
548/*
549 * Mark the job "pt" with "copyID".
550 * Also see set_ref_in_item().
551 */
552 static int
553set_ref_in_item_job(
554 job_T *job,
555 int copyID,
556 ht_stack_T **ht_stack,
557 list_stack_T **list_stack)
558{
559 typval_T dtv;
560
561 if (job == NULL || job->jv_copyID == copyID)
562 return FALSE;
563
564 job->jv_copyID = copyID;
565 if (job->jv_channel != NULL)
566 {
567 dtv.v_type = VAR_CHANNEL;
568 dtv.vval.v_channel = job->jv_channel;
569 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
570 }
571 if (job->jv_exit_cb.cb_partial != NULL)
572 {
573 dtv.v_type = VAR_PARTIAL;
574 dtv.vval.v_partial = job->jv_exit_cb.cb_partial;
575 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
576 }
577
578 return FALSE;
579}
580
581/*
582 * Mark the channel "ch" with "copyID".
583 * Also see set_ref_in_item().
584 */
585 static int
586set_ref_in_item_channel(
587 channel_T *ch,
588 int copyID,
589 ht_stack_T **ht_stack,
590 list_stack_T **list_stack)
591{
592 typval_T dtv;
593
594 if (ch == NULL || ch->ch_copyID == copyID)
595 return FALSE;
596
597 ch->ch_copyID = copyID;
598 for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part)
599 {
600 for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next;
601 jq != NULL; jq = jq->jq_next)
602 set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack);
603 for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL;
604 cq = cq->cq_next)
605 if (cq->cq_callback.cb_partial != NULL)
606 {
607 dtv.v_type = VAR_PARTIAL;
608 dtv.vval.v_partial = cq->cq_callback.cb_partial;
609 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
610 }
611 if (ch->ch_part[part].ch_callback.cb_partial != NULL)
612 {
613 dtv.v_type = VAR_PARTIAL;
614 dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial;
615 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
616 }
617 }
618 if (ch->ch_callback.cb_partial != NULL)
619 {
620 dtv.v_type = VAR_PARTIAL;
621 dtv.vval.v_partial = ch->ch_callback.cb_partial;
622 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
623 }
624 if (ch->ch_close_cb.cb_partial != NULL)
625 {
626 dtv.v_type = VAR_PARTIAL;
627 dtv.vval.v_partial = ch->ch_close_cb.cb_partial;
628 set_ref_in_item(&dtv, copyID, ht_stack, list_stack);
629 }
630
631 return FALSE;
632}
633#endif
634
635/*
636 * Mark the class "cl" with "copyID".
637 * Also see set_ref_in_item().
638 */
639 int
640set_ref_in_item_class(
641 class_T *cl,
642 int copyID,
643 ht_stack_T **ht_stack,
644 list_stack_T **list_stack)
645{
646 int abort = FALSE;
647
648 if (cl == NULL || cl->class_copyID == copyID)
649 return FALSE;
650
651 cl->class_copyID = copyID;
652 if (cl->class_members_tv != NULL)
653 {
654 // The "class_members_tv" table is allocated only for regular classes
655 // and not for interfaces.
656 for (int i = 0; !abort && i < cl->class_class_member_count; ++i)
657 abort = abort || set_ref_in_item(
658 &cl->class_members_tv[i],
659 copyID, ht_stack, list_stack);
660 }
661
662 for (int i = 0; !abort && i < cl->class_class_function_count; ++i)
663 abort = abort || set_ref_in_func(NULL,
664 cl->class_class_functions[i], copyID);
665
666 for (int i = 0; !abort && i < cl->class_obj_method_count; ++i)
667 abort = abort || set_ref_in_func(NULL,
668 cl->class_obj_methods[i], copyID);
669
670 return abort;
671}
672
673/*
674 * Mark the object "cl" with "copyID".
675 * Also see set_ref_in_item().
676 */
677 static int
678set_ref_in_item_object(
679 object_T *obj,
680 int copyID,
681 ht_stack_T **ht_stack,
682 list_stack_T **list_stack)
683{
684 int abort = FALSE;
685
686 if (obj == NULL || obj->obj_copyID == copyID)
687 return FALSE;
688
689 obj->obj_copyID = copyID;
690
691 // The typval_T array is right after the object_T.
692 typval_T *mtv = (typval_T *)(obj + 1);
693 for (int i = 0; !abort
694 && i < obj->obj_class->class_obj_member_count; ++i)
695 abort = abort || set_ref_in_item(mtv + i, copyID,
696 ht_stack, list_stack);
697
698 return abort;
699}
700
701/*
702 * Mark all lists, dicts and other container types referenced through typval
703 * "tv" with "copyID".
704 * "list_stack" is used to add lists to be marked. Can be NULL.
705 * "ht_stack" is used to add hashtabs to be marked. Can be NULL.
706 *
707 * Returns TRUE if setting references failed somehow.
708 */
709 int
710set_ref_in_item(
711 typval_T *tv,
712 int copyID,
713 ht_stack_T **ht_stack,
714 list_stack_T **list_stack)
715{
716 int abort = FALSE;
717
718 switch (tv->v_type)
719 {
720 case VAR_DICT:
721 return set_ref_in_item_dict(tv->vval.v_dict, copyID,
722 ht_stack, list_stack);
723
724 case VAR_LIST:
725 return set_ref_in_item_list(tv->vval.v_list, copyID,
726 ht_stack, list_stack);
727
728 case VAR_FUNC:
729 {
730 abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
731 break;
732 }
733
734 case VAR_PARTIAL:
735 return set_ref_in_item_partial(tv->vval.v_partial, copyID,
736 ht_stack, list_stack);
737
738 case VAR_JOB:
739#ifdef FEAT_JOB_CHANNEL
740 return set_ref_in_item_job(tv->vval.v_job, copyID,
741 ht_stack, list_stack);
742#else
743 break;
744#endif
745
746 case VAR_CHANNEL:
747#ifdef FEAT_JOB_CHANNEL
748 return set_ref_in_item_channel(tv->vval.v_channel, copyID,
749 ht_stack, list_stack);
750#else
751 break;
752#endif
753
754 case VAR_CLASS:
755 return set_ref_in_item_class(tv->vval.v_class, copyID,
756 ht_stack, list_stack);
757
758 case VAR_OBJECT:
759 return set_ref_in_item_object(tv->vval.v_object, copyID,
760 ht_stack, list_stack);
761
762 case VAR_UNKNOWN:
763 case VAR_ANY:
764 case VAR_VOID:
765 case VAR_BOOL:
766 case VAR_SPECIAL:
767 case VAR_NUMBER:
768 case VAR_FLOAT:
769 case VAR_STRING:
770 case VAR_BLOB:
771 case VAR_TYPEALIAS:
772 case VAR_INSTR:
773 // Types that do not contain any other item
774 break;
775 }
776
777 return abort;
778}
779
780#endif