blob: b95b2cad29307414324db6343ff03948c539df3c [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,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100125 NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200126
127 // window-local variables
128 FOR_ALL_TAB_WINDOWS(tp, wp)
129 abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100130 NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200131 // 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(
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100135 &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200136#ifdef FEAT_PROP_POPUP
137 FOR_ALL_POPUPWINS(wp)
138 abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100139 NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200140 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,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100143 NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200144#endif
145
146 // tabpage-local variables
147 FOR_ALL_TABPAGES(tp)
148 abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100149 NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200150 // 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
Yegappan Lakshmanana13f3a42024-11-02 18:40:10 +0100186 // 'findfunc' callback
187 abort = abort || set_ref_in_findfunc(copyID);
188
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200189#ifdef FEAT_LUA
190 abort = abort || set_ref_in_lua(copyID);
191#endif
192
193#ifdef FEAT_PYTHON
194 abort = abort || set_ref_in_python(copyID);
195#endif
196
197#ifdef FEAT_PYTHON3
198 abort = abort || set_ref_in_python3(copyID);
199#endif
200
201#ifdef FEAT_JOB_CHANNEL
202 abort = abort || set_ref_in_channel(copyID);
203 abort = abort || set_ref_in_job(copyID);
204#endif
205#ifdef FEAT_NETBEANS_INTG
206 abort = abort || set_ref_in_nb_channel(copyID);
207#endif
208
209#ifdef FEAT_TIMERS
210 abort = abort || set_ref_in_timer(copyID);
211#endif
212
213#ifdef FEAT_QUICKFIX
214 abort = abort || set_ref_in_quickfix(copyID);
215#endif
216
217#ifdef FEAT_TERMINAL
218 abort = abort || set_ref_in_term(copyID);
219#endif
220
221#ifdef FEAT_PROP_POPUP
222 abort = abort || set_ref_in_popups(copyID);
223#endif
224
225 abort = abort || set_ref_in_classes(copyID);
226
227 if (!abort)
228 {
229 /*
230 * 2. Free lists and dictionaries that are not referenced.
231 */
232 did_free = free_unref_items(copyID);
233
234 /*
235 * 3. Check if any funccal can be freed now.
236 * This may call us back recursively.
237 */
238 free_unref_funccal(copyID, testing);
239 }
240 else if (p_verbose > 0)
241 {
242 verb_msg(_("Not enough memory to set references, garbage collection aborted!"));
243 }
244
245 return did_free;
246}
247
248/*
249 * Free lists, dictionaries, channels and jobs that are no longer referenced.
250 */
251 static int
252free_unref_items(int copyID)
253{
254 int did_free = FALSE;
255
256 // Let all "free" functions know that we are here. This means no
257 // dictionaries, lists, channels or jobs are to be freed, because we will
258 // do that here.
259 in_free_unref_items = TRUE;
260
261 /*
262 * PASS 1: free the contents of the items. We don't free the items
263 * themselves yet, so that it is possible to decrement refcount counters
264 */
265
266 // Go through the list of dicts and free items without this copyID.
267 did_free |= dict_free_nonref(copyID);
268
269 // Go through the list of lists and free items without this copyID.
270 did_free |= list_free_nonref(copyID);
271
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100272 // Go through the list of tuples and free items without this copyID.
273 did_free |= tuple_free_nonref(copyID);
274
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200275 // Go through the list of objects and free items without this copyID.
276 did_free |= object_free_nonref(copyID);
277
278 // Go through the list of classes and free items without this copyID.
279 did_free |= class_free_nonref(copyID);
280
281#ifdef FEAT_JOB_CHANNEL
282 // Go through the list of jobs and free items without the copyID. This
283 // must happen before doing channels, because jobs refer to channels, but
284 // the reference from the channel to the job isn't tracked.
285 did_free |= free_unused_jobs_contents(copyID, COPYID_MASK);
286
287 // Go through the list of channels and free items without the copyID.
288 did_free |= free_unused_channels_contents(copyID, COPYID_MASK);
289#endif
290
291 /*
292 * PASS 2: free the items themselves.
293 */
294 object_free_items(copyID);
295 dict_free_items(copyID);
296 list_free_items(copyID);
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100297 tuple_free_items(copyID);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200298
299#ifdef FEAT_JOB_CHANNEL
300 // Go through the list of jobs and free items without the copyID. This
301 // must happen before doing channels, because jobs refer to channels, but
302 // the reference from the channel to the job isn't tracked.
303 free_unused_jobs(copyID, COPYID_MASK);
304
305 // Go through the list of channels and free items without the copyID.
306 free_unused_channels(copyID, COPYID_MASK);
307#endif
308
309 in_free_unref_items = FALSE;
310
311 return did_free;
312}
313
314/*
315 * Mark all lists and dicts referenced through hashtab "ht" with "copyID".
316 * "list_stack" is used to add lists to be marked. Can be NULL.
317 *
318 * Returns TRUE if setting references failed somehow.
319 */
320 int
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100321set_ref_in_ht(
322 hashtab_T *ht,
323 int copyID,
324 list_stack_T **list_stack,
325 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200326{
327 int todo;
328 int abort = FALSE;
329 hashitem_T *hi;
330 hashtab_T *cur_ht;
331 ht_stack_T *ht_stack = NULL;
332 ht_stack_T *tempitem;
333
334 cur_ht = ht;
335 for (;;)
336 {
337 if (!abort)
338 {
339 // Mark each item in the hashtab. If the item contains a hashtab
340 // it is added to ht_stack, if it contains a list it is added to
341 // list_stack.
342 todo = (int)cur_ht->ht_used;
343 FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo)
344 if (!HASHITEM_EMPTY(hi))
345 {
346 --todo;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100347 abort = abort
348 || set_ref_in_item(&HI2DI(hi)->di_tv, copyID,
349 &ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200350 }
351 }
352
353 if (ht_stack == NULL)
354 break;
355
356 // take an item from the stack
357 cur_ht = ht_stack->ht;
358 tempitem = ht_stack;
359 ht_stack = ht_stack->prev;
360 free(tempitem);
361 }
362
363 return abort;
364}
365
366#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \
367 || defined(PROTO)
368/*
369 * Mark a dict and its items with "copyID".
370 * Returns TRUE if setting references failed somehow.
371 */
372 int
373set_ref_in_dict(dict_T *d, int copyID)
374{
375 if (d != NULL && d->dv_copyID != copyID)
376 {
377 d->dv_copyID = copyID;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100378 return set_ref_in_ht(&d->dv_hashtab, copyID, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200379 }
380 return FALSE;
381}
382#endif
383
384/*
385 * Mark a list and its items with "copyID".
386 * Returns TRUE if setting references failed somehow.
387 */
388 int
389set_ref_in_list(list_T *ll, int copyID)
390{
391 if (ll != NULL && ll->lv_copyID != copyID)
392 {
393 ll->lv_copyID = copyID;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100394 return set_ref_in_list_items(ll, copyID, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200395 }
396 return FALSE;
397}
398
399/*
400 * Mark all lists and dicts referenced through list "l" with "copyID".
401 * "ht_stack" is used to add hashtabs to be marked. Can be NULL.
402 *
403 * Returns TRUE if setting references failed somehow.
404 */
405 int
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100406set_ref_in_list_items(
407 list_T *l,
408 int copyID,
409 ht_stack_T **ht_stack,
410 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200411{
412 listitem_T *li;
413 int abort = FALSE;
414 list_T *cur_l;
415 list_stack_T *list_stack = NULL;
416 list_stack_T *tempitem;
417
418 cur_l = l;
419 for (;;)
420 {
421 if (!abort && cur_l->lv_first != &range_list_item)
422 // Mark each item in the list. If the item contains a hashtab
423 // it is added to ht_stack, if it contains a list it is added to
424 // list_stack.
425 for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next)
426 abort = abort || set_ref_in_item(&li->li_tv, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100427 ht_stack, &list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200428 if (list_stack == NULL)
429 break;
430
431 // take an item from the stack
432 cur_l = list_stack->list;
433 tempitem = list_stack;
434 list_stack = list_stack->prev;
435 free(tempitem);
436 }
437
438 return abort;
439}
440
441/*
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100442 * Mark all lists and dicts referenced through tuple "t" with "copyID".
443 * "ht_stack" is used to add hashtabs to be marked. Can be NULL.
444 *
445 * Returns TRUE if setting references failed somehow.
446 */
447 int
448set_ref_in_tuple_items(
449 tuple_T *tuple,
450 int copyID,
451 ht_stack_T **ht_stack,
452 list_stack_T **list_stack)
453{
454 int abort = FALSE;
455 tuple_T *cur_t;
456 tuple_stack_T *tuple_stack = NULL;
457 tuple_stack_T *tempitem;
458
459 cur_t = tuple;
460 for (;;)
461 {
462 // Mark each item in the tuple. If the item contains a hashtab
463 // it is added to ht_stack, if it contains a list it is added to
464 // list_stack.
465 for (int i = 0; i < cur_t->tv_items.ga_len; i++)
466 {
467 typval_T *tv = ((typval_T *)cur_t->tv_items.ga_data) + i;
468 abort = abort
469 || set_ref_in_item(tv, copyID,
470 ht_stack, list_stack, &tuple_stack);
471 }
472 if (tuple_stack == NULL)
473 break;
474
475 // take an item from the stack
476 cur_t = tuple_stack->tuple;
477 tempitem = tuple_stack;
478 tuple_stack = tuple_stack->prev;
479 free(tempitem);
480 }
481
482 return abort;
483}
484
485/*
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200486 * Mark the partial in callback 'cb' with "copyID".
487 */
488 int
489set_ref_in_callback(callback_T *cb, int copyID)
490{
491 typval_T tv;
492
493 if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL)
494 return FALSE;
495
496 tv.v_type = VAR_PARTIAL;
497 tv.vval.v_partial = cb->cb_partial;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100498 return set_ref_in_item(&tv, copyID, NULL, NULL, NULL);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200499}
500
501/*
502 * Mark the dict "dd" with "copyID".
503 * Also see set_ref_in_item().
504 */
505 static int
506set_ref_in_item_dict(
507 dict_T *dd,
508 int copyID,
509 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100510 list_stack_T **list_stack,
511 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200512{
513 if (dd == NULL || dd->dv_copyID == copyID)
514 return FALSE;
515
516 // Didn't see this dict yet.
517 dd->dv_copyID = copyID;
518 if (ht_stack == NULL)
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100519 return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200520
521 ht_stack_T *newitem = ALLOC_ONE(ht_stack_T);
522 if (newitem == NULL)
523 return TRUE;
524
525 newitem->ht = &dd->dv_hashtab;
526 newitem->prev = *ht_stack;
527 *ht_stack = newitem;
528
529 return FALSE;
530}
531
532/*
533 * Mark the list "ll" with "copyID".
534 * Also see set_ref_in_item().
535 */
536 static int
537set_ref_in_item_list(
538 list_T *ll,
539 int copyID,
540 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100541 list_stack_T **list_stack,
542 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200543{
544 if (ll == NULL || ll->lv_copyID == copyID)
545 return FALSE;
546
547 // Didn't see this list yet.
548 ll->lv_copyID = copyID;
549 if (list_stack == NULL)
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100550 return set_ref_in_list_items(ll, copyID, ht_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200551
552 list_stack_T *newitem = ALLOC_ONE(list_stack_T);
553 if (newitem == NULL)
554 return TRUE;
555
556 newitem->list = ll;
557 newitem->prev = *list_stack;
558 *list_stack = newitem;
559
560 return FALSE;
561}
562
563/*
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100564 * Mark the tuple "tt" with "copyID".
565 * Also see set_ref_in_item().
566 */
567 static int
568set_ref_in_item_tuple(
569 tuple_T *tt,
570 int copyID,
571 ht_stack_T **ht_stack,
572 list_stack_T **list_stack,
573 tuple_stack_T **tuple_stack)
574{
575 if (tt == NULL || tt->tv_copyID == copyID)
576 return FALSE;
577
578 // Didn't see this tuple yet.
579 tt->tv_copyID = copyID;
580 if (tuple_stack == NULL)
581 return set_ref_in_tuple_items(tt, copyID, ht_stack, list_stack);
582
583 tuple_stack_T *newitem = ALLOC_ONE(tuple_stack_T);
584 if (newitem == NULL)
585 return TRUE;
586
587 newitem->tuple = tt;
588 newitem->prev = *tuple_stack;
589 *tuple_stack = newitem;
590
591 return FALSE;
592}
593
594/*
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200595 * Mark the partial "pt" with "copyID".
596 * Also see set_ref_in_item().
597 */
598 static int
599set_ref_in_item_partial(
600 partial_T *pt,
601 int copyID,
602 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100603 list_stack_T **list_stack,
604 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200605{
606 if (pt == NULL || pt->pt_copyID == copyID)
607 return FALSE;
608
609 // Didn't see this partial yet.
610 pt->pt_copyID = copyID;
611
612 int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID);
613
614 if (pt->pt_dict != NULL)
615 {
616 typval_T dtv;
617
618 dtv.v_type = VAR_DICT;
619 dtv.vval.v_dict = pt->pt_dict;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100620 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200621 }
622
623 if (pt->pt_obj != NULL)
624 {
625 typval_T objtv;
626
627 objtv.v_type = VAR_OBJECT;
628 objtv.vval.v_object = pt->pt_obj;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100629 set_ref_in_item(&objtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200630 }
631
632 for (int i = 0; i < pt->pt_argc; ++i)
633 abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100634 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200635 // pt_funcstack is handled in set_ref_in_funcstacks()
636 // pt_loopvars is handled in set_ref_in_loopvars()
637
638 return abort;
639}
640
641#ifdef FEAT_JOB_CHANNEL
642/*
643 * Mark the job "pt" with "copyID".
644 * Also see set_ref_in_item().
645 */
646 static int
647set_ref_in_item_job(
648 job_T *job,
649 int copyID,
650 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100651 list_stack_T **list_stack,
652 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200653{
654 typval_T dtv;
655
656 if (job == NULL || job->jv_copyID == copyID)
657 return FALSE;
658
659 job->jv_copyID = copyID;
660 if (job->jv_channel != NULL)
661 {
662 dtv.v_type = VAR_CHANNEL;
663 dtv.vval.v_channel = job->jv_channel;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100664 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200665 }
666 if (job->jv_exit_cb.cb_partial != NULL)
667 {
668 dtv.v_type = VAR_PARTIAL;
669 dtv.vval.v_partial = job->jv_exit_cb.cb_partial;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100670 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200671 }
672
673 return FALSE;
674}
675
676/*
677 * Mark the channel "ch" with "copyID".
678 * Also see set_ref_in_item().
679 */
680 static int
681set_ref_in_item_channel(
682 channel_T *ch,
683 int copyID,
684 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100685 list_stack_T **list_stack,
686 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200687{
688 typval_T dtv;
689
690 if (ch == NULL || ch->ch_copyID == copyID)
691 return FALSE;
692
693 ch->ch_copyID = copyID;
694 for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part)
695 {
696 for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next;
697 jq != NULL; jq = jq->jq_next)
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100698 set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200699 for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL;
700 cq = cq->cq_next)
701 if (cq->cq_callback.cb_partial != NULL)
702 {
703 dtv.v_type = VAR_PARTIAL;
704 dtv.vval.v_partial = cq->cq_callback.cb_partial;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100705 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200706 }
707 if (ch->ch_part[part].ch_callback.cb_partial != NULL)
708 {
709 dtv.v_type = VAR_PARTIAL;
710 dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100711 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200712 }
713 }
714 if (ch->ch_callback.cb_partial != NULL)
715 {
716 dtv.v_type = VAR_PARTIAL;
717 dtv.vval.v_partial = ch->ch_callback.cb_partial;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100718 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200719 }
720 if (ch->ch_close_cb.cb_partial != NULL)
721 {
722 dtv.v_type = VAR_PARTIAL;
723 dtv.vval.v_partial = ch->ch_close_cb.cb_partial;
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100724 set_ref_in_item(&dtv, copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200725 }
726
727 return FALSE;
728}
729#endif
730
731/*
732 * Mark the class "cl" with "copyID".
733 * Also see set_ref_in_item().
734 */
735 int
736set_ref_in_item_class(
737 class_T *cl,
738 int copyID,
739 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100740 list_stack_T **list_stack,
741 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200742{
743 int abort = FALSE;
744
745 if (cl == NULL || cl->class_copyID == copyID)
746 return FALSE;
747
748 cl->class_copyID = copyID;
749 if (cl->class_members_tv != NULL)
750 {
751 // The "class_members_tv" table is allocated only for regular classes
752 // and not for interfaces.
753 for (int i = 0; !abort && i < cl->class_class_member_count; ++i)
754 abort = abort || set_ref_in_item(
755 &cl->class_members_tv[i],
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100756 copyID, ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200757 }
758
759 for (int i = 0; !abort && i < cl->class_class_function_count; ++i)
760 abort = abort || set_ref_in_func(NULL,
761 cl->class_class_functions[i], copyID);
762
763 for (int i = 0; !abort && i < cl->class_obj_method_count; ++i)
764 abort = abort || set_ref_in_func(NULL,
765 cl->class_obj_methods[i], copyID);
766
767 return abort;
768}
769
770/*
771 * Mark the object "cl" with "copyID".
772 * Also see set_ref_in_item().
773 */
774 static int
775set_ref_in_item_object(
776 object_T *obj,
777 int copyID,
778 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100779 list_stack_T **list_stack,
780 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200781{
782 int abort = FALSE;
783
784 if (obj == NULL || obj->obj_copyID == copyID)
785 return FALSE;
786
787 obj->obj_copyID = copyID;
788
789 // The typval_T array is right after the object_T.
790 typval_T *mtv = (typval_T *)(obj + 1);
791 for (int i = 0; !abort
792 && i < obj->obj_class->class_obj_member_count; ++i)
793 abort = abort || set_ref_in_item(mtv + i, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100794 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200795
796 return abort;
797}
798
799/*
800 * Mark all lists, dicts and other container types referenced through typval
801 * "tv" with "copyID".
802 * "list_stack" is used to add lists to be marked. Can be NULL.
803 * "ht_stack" is used to add hashtabs to be marked. Can be NULL.
804 *
805 * Returns TRUE if setting references failed somehow.
806 */
807 int
808set_ref_in_item(
809 typval_T *tv,
810 int copyID,
811 ht_stack_T **ht_stack,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100812 list_stack_T **list_stack,
813 tuple_stack_T **tuple_stack)
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200814{
815 int abort = FALSE;
816
817 switch (tv->v_type)
818 {
819 case VAR_DICT:
820 return set_ref_in_item_dict(tv->vval.v_dict, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100821 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200822
823 case VAR_LIST:
824 return set_ref_in_item_list(tv->vval.v_list, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100825 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200826
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100827 case VAR_TUPLE:
828 return set_ref_in_item_tuple(tv->vval.v_tuple, copyID,
829 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200830 case VAR_FUNC:
831 {
832 abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
833 break;
834 }
835
836 case VAR_PARTIAL:
837 return set_ref_in_item_partial(tv->vval.v_partial, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100838 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200839
840 case VAR_JOB:
841#ifdef FEAT_JOB_CHANNEL
842 return set_ref_in_item_job(tv->vval.v_job, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100843 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200844#else
845 break;
846#endif
847
848 case VAR_CHANNEL:
849#ifdef FEAT_JOB_CHANNEL
850 return set_ref_in_item_channel(tv->vval.v_channel, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100851 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200852#else
853 break;
854#endif
855
856 case VAR_CLASS:
857 return set_ref_in_item_class(tv->vval.v_class, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100858 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200859
860 case VAR_OBJECT:
861 return set_ref_in_item_object(tv->vval.v_object, copyID,
Yegappan Lakshmanan9cb865e2025-03-23 16:42:16 +0100862 ht_stack, list_stack, tuple_stack);
Yegappan Lakshmanan25536f42024-05-22 16:45:04 +0200863
864 case VAR_UNKNOWN:
865 case VAR_ANY:
866 case VAR_VOID:
867 case VAR_BOOL:
868 case VAR_SPECIAL:
869 case VAR_NUMBER:
870 case VAR_FLOAT:
871 case VAR_STRING:
872 case VAR_BLOB:
873 case VAR_TYPEALIAS:
874 case VAR_INSTR:
875 // Types that do not contain any other item
876 break;
877 }
878
879 return abort;
880}
881
882#endif