Ruby 4.0.5p0 (2026-05-20 revision 64336ffd0ee9e1f4c05891695a3d7b49cb709721)
weakmap.c
1#include "internal.h"
2#include "internal/gc.h"
3#include "internal/hash.h"
4#include "internal/proc.h"
5#include "internal/sanitizers.h"
6#include "ruby/st.h"
7
8/* ===== WeakMap =====
9 *
10 * WeakMap contains one ST table which contains a pointer to the object as the
11 * key and a pointer to the object as the value. This means that the key and
12 * value of the table are both of the type `VALUE *`.
13 *
14 * The objects are not directly stored as keys and values in the table because
15 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
16 * when the object is reclaimed. Using a pointer into the ST table entry is not
17 * safe because the pointer can change when the ST table is resized.
18 *
19 * WeakMap hashes and compares using the pointer address of the object.
20 *
21 * For performance and memory efficiency reasons, the key and value
22 * are allocated at the same time and adjacent to each other.
23 *
24 * During GC and while iterating, reclaimed entries (i.e. either the key or
25 * value points to `Qundef`) are removed from the ST table.
26 */
27
28struct weakmap {
29 st_table *table;
30};
31
33 VALUE key;
34 VALUE val;
35};
36
37static bool
38wmap_live_p(VALUE obj)
39{
40 return !UNDEF_P(obj);
41}
42
44 int (*func)(struct weakmap_entry *, st_data_t);
45 st_data_t arg;
46
47 struct weakmap_entry *dead_entry;
48};
49
50static int
51wmap_foreach_i(st_data_t key, st_data_t val, st_data_t arg)
52{
53 struct wmap_foreach_data *data = (struct wmap_foreach_data *)arg;
54
55 if (data->dead_entry != NULL) {
56 ruby_sized_xfree(data->dead_entry, sizeof(struct weakmap_entry));
57 data->dead_entry = NULL;
58 }
59
60 struct weakmap_entry *entry = (struct weakmap_entry *)key;
61 RUBY_ASSERT(&entry->val == (VALUE *)val);
62
63 if (wmap_live_p(entry->key) && wmap_live_p(entry->val)) {
64 VALUE k = entry->key;
65 VALUE v = entry->val;
66
67 int ret = data->func(entry, data->arg);
68
69 RB_GC_GUARD(k);
70 RB_GC_GUARD(v);
71
72 return ret;
73 }
74 else {
75 /* We cannot free the weakmap_entry here because the ST_DELETE could
76 * hash the key which would read the weakmap_entry and would cause a
77 * use-after-free. Instead, we store this entry and free it on the next
78 * iteration. */
79 data->dead_entry = entry;
80
81 return ST_DELETE;
82 }
83}
84
85static void
86wmap_foreach(struct weakmap *w, int (*func)(struct weakmap_entry *, st_data_t), st_data_t arg)
87{
88 struct wmap_foreach_data foreach_data = {
89 .func = func,
90 .arg = arg,
91 .dead_entry = NULL,
92 };
93
94 st_foreach(w->table, wmap_foreach_i, (st_data_t)&foreach_data);
95
96 ruby_sized_xfree(foreach_data.dead_entry, sizeof(struct weakmap_entry));
97}
98
99static int
100wmap_mark_weak_table_i(struct weakmap_entry *entry, st_data_t _)
101{
102 rb_gc_mark_weak(&entry->key);
103 rb_gc_mark_weak(&entry->val);
104
105 return ST_CONTINUE;
106}
107
108static void
109wmap_mark(void *ptr)
110{
111 struct weakmap *w = ptr;
112 if (w->table) {
113 wmap_foreach(w, wmap_mark_weak_table_i, (st_data_t)0);
114 }
115}
116
117static int
118wmap_free_table_i(st_data_t key, st_data_t val, st_data_t arg)
119{
120 struct weakmap_entry *entry = (struct weakmap_entry *)key;
121 RUBY_ASSERT(&entry->val == (VALUE *)val);
122 ruby_sized_xfree(entry, sizeof(struct weakmap_entry));
123
124 return ST_CONTINUE;
125}
126
127static void
128wmap_free(void *ptr)
129{
130 struct weakmap *w = ptr;
131
132 st_foreach(w->table, wmap_free_table_i, 0);
133 st_free_table(w->table);
134}
135
136static size_t
137wmap_memsize(const void *ptr)
138{
139 const struct weakmap *w = ptr;
140
141 size_t size = 0;
142 if (w->table) {
143 size += st_memsize(w->table);
144 /* The key and value of the table each take sizeof(VALUE) in size. */
145 size += st_table_size(w->table) * (2 * sizeof(VALUE));
146 }
147
148 return size;
149}
150
152 st_table *table;
153 struct weakmap_entry *dead_entry;
154};
155
156static int
157wmap_compact_table_i(st_data_t key, st_data_t val, st_data_t d)
158{
159 struct wmap_compact_table_data *data = (struct wmap_compact_table_data *)d;
160 if (data->dead_entry != NULL) {
161 ruby_sized_xfree(data->dead_entry, sizeof(struct weakmap_entry));
162 data->dead_entry = NULL;
163 }
164
165 struct weakmap_entry *entry = (struct weakmap_entry *)key;
166
167 entry->val = rb_gc_location(entry->val);
168
169 VALUE new_key = rb_gc_location(entry->key);
170
171 /* If the key object moves, then we must reinsert because the hash is
172 * based on the pointer rather than the object itself. */
173 if (entry->key != new_key) {
174 DURING_GC_COULD_MALLOC_REGION_START();
175 {
176 struct weakmap_entry *new_entry = xmalloc(sizeof(struct weakmap_entry));
177 new_entry->key = new_key;
178 new_entry->val = entry->val;
179 st_insert(data->table, (st_data_t)&new_entry->key, (st_data_t)&new_entry->val);
180 }
181 DURING_GC_COULD_MALLOC_REGION_END();
182
183 /* We cannot free the weakmap_entry here because the ST_DELETE could
184 * hash the key which would read the weakmap_entry and would cause a
185 * use-after-free. Instead, we store this entry and free it on the next
186 * iteration. */
187 data->dead_entry = entry;
188
189 return ST_DELETE;
190 }
191
192 return ST_CONTINUE;
193}
194
195static void
196wmap_compact(void *ptr)
197{
198 struct weakmap *w = ptr;
199
200 if (w->table) {
201 struct wmap_compact_table_data compact_data = {
202 .table = w->table,
203 .dead_entry = NULL,
204 };
205
206 st_foreach(w->table, wmap_compact_table_i, (st_data_t)&compact_data);
207
208 ruby_sized_xfree(compact_data.dead_entry, sizeof(struct weakmap_entry));
209 }
210}
211
212static const rb_data_type_t weakmap_type = {
213 "weakmap",
214 {
215 wmap_mark,
216 wmap_free,
217 wmap_memsize,
218 wmap_compact,
219 },
220 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
221};
222
223static int
224wmap_cmp(st_data_t x, st_data_t y)
225{
226 VALUE x_obj = *(VALUE *)x;
227 VALUE y_obj = *(VALUE *)y;
228
229 if (!wmap_live_p(x_obj) && !wmap_live_p(y_obj)) {
230 return x != y;
231 }
232 else {
233 return x_obj != y_obj;
234 }
235}
236
237static st_index_t
238wmap_hash(st_data_t n)
239{
240 return st_numhash(*(VALUE *)n);
241}
242
243static const struct st_hash_type wmap_hash_type = {
244 wmap_cmp,
245 wmap_hash,
246};
247
248static VALUE
249wmap_allocate(VALUE klass)
250{
251 struct weakmap *w;
252 VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &weakmap_type, w);
253 w->table = st_init_table(&wmap_hash_type);
254 return obj;
255}
256
257static VALUE
258wmap_inspect_append(VALUE str, VALUE obj)
259{
260 if (SPECIAL_CONST_P(obj)) {
261 return rb_str_append(str, rb_inspect(obj));
262 }
263 else {
264 return rb_str_append(str, rb_any_to_s(obj));
265 }
266}
267
268static int
269wmap_inspect_i(struct weakmap_entry *entry, st_data_t data)
270{
271 VALUE str = (VALUE)data;
272
273 if (RSTRING_PTR(str)[0] == '#') {
274 rb_str_cat2(str, ", ");
275 }
276 else {
277 rb_str_cat2(str, ": ");
278 RSTRING_PTR(str)[0] = '#';
279 }
280
281 wmap_inspect_append(str, entry->key);
282 rb_str_cat2(str, " => ");
283 wmap_inspect_append(str, entry->val);
284
285 return ST_CONTINUE;
286}
287
288static VALUE
289wmap_inspect(VALUE self)
290{
291 VALUE c = rb_class_name(CLASS_OF(self));
292 struct weakmap *w;
293 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
294
295 VALUE str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self);
296
297 wmap_foreach(w, wmap_inspect_i, (st_data_t)str);
298
299 RSTRING_PTR(str)[0] = '#';
300 rb_str_cat2(str, ">");
301
302 return str;
303}
304
305static int
306wmap_each_i(struct weakmap_entry *entry, st_data_t _)
307{
308 rb_yield_values(2, entry->key, entry->val);
309
310 return ST_CONTINUE;
311}
312
313/*
314 * call-seq:
315 * map.each {|key, val| ... } -> self
316 *
317 * Iterates over keys and values. Note that unlike other collections,
318 * +each+ without block isn't supported.
319 *
320 */
321static VALUE
322wmap_each(VALUE self)
323{
324 struct weakmap *w;
325 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
326
327 wmap_foreach(w, wmap_each_i, (st_data_t)0);
328
329 return self;
330}
331
332static int
333wmap_each_key_i(struct weakmap_entry *entry, st_data_t _data)
334{
335 rb_yield(entry->key);
336
337 return ST_CONTINUE;
338}
339
340/*
341 * call-seq:
342 * map.each_key {|key| ... } -> self
343 *
344 * Iterates over keys. Note that unlike other collections,
345 * +each_key+ without block isn't supported.
346 *
347 */
348static VALUE
349wmap_each_key(VALUE self)
350{
351 struct weakmap *w;
352 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
353
354 wmap_foreach(w, wmap_each_key_i, (st_data_t)0);
355
356 return self;
357}
358
359static int
360wmap_each_value_i(struct weakmap_entry *entry, st_data_t _data)
361{
362 rb_yield(entry->val);
363
364 return ST_CONTINUE;
365}
366
367/*
368 * call-seq:
369 * map.each_value {|val| ... } -> self
370 *
371 * Iterates over values. Note that unlike other collections,
372 * +each_value+ without block isn't supported.
373 *
374 */
375static VALUE
376wmap_each_value(VALUE self)
377{
378 struct weakmap *w;
379 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
380
381 wmap_foreach(w, wmap_each_value_i, (st_data_t)0);
382
383 return self;
384}
385
386static int
387wmap_keys_i(struct weakmap_entry *entry, st_data_t arg)
388{
389 VALUE ary = (VALUE)arg;
390
391 rb_ary_push(ary, entry->key);
392
393 return ST_CONTINUE;
394}
395
396/*
397 * call-seq:
398 * map.keys -> new_array
399 *
400 * Returns a new Array containing all keys in the map.
401 *
402 */
403static VALUE
404wmap_keys(VALUE self)
405{
406 struct weakmap *w;
407 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
408
409 VALUE ary = rb_ary_new();
410 wmap_foreach(w, wmap_keys_i, (st_data_t)ary);
411
412 return ary;
413}
414
415static int
416wmap_values_i(struct weakmap_entry *entry, st_data_t arg)
417{
418 VALUE ary = (VALUE)arg;
419
420 rb_ary_push(ary, entry->val);
421
422 return ST_CONTINUE;
423}
424
425/*
426 * call-seq:
427 * map.values -> new_array
428 *
429 * Returns a new Array containing all values in the map.
430 *
431 */
432static VALUE
433wmap_values(VALUE self)
434{
435 struct weakmap *w;
436 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
437
438 VALUE ary = rb_ary_new();
439 wmap_foreach(w, wmap_values_i, (st_data_t)ary);
440
441 return ary;
442}
443
444static int
445wmap_aset_replace(st_data_t *key, st_data_t *val, st_data_t new_key_ptr, int existing)
446{
447 VALUE new_key = *(VALUE *)new_key_ptr;
448 VALUE new_val = *(((VALUE *)new_key_ptr) + 1);
449
450 if (existing) {
451 RUBY_ASSERT(*(VALUE *)*key == new_key);
452 }
453 else {
454 struct weakmap_entry *entry = xmalloc(sizeof(struct weakmap_entry));
455
456 *key = (st_data_t)&entry->key;
457 *val = (st_data_t)&entry->val;
458 }
459
460 *(VALUE *)*key = new_key;
461 *(VALUE *)*val = new_val;
462
463 return ST_CONTINUE;
464}
465
466/*
467 * call-seq:
468 * map[key] = value -> value
469 *
470 * Associates the given +value+ with the given +key+.
471 *
472 * If the given +key+ exists, replaces its value with the given +value+;
473 * the ordering is not affected.
474 */
475static VALUE
476wmap_aset(VALUE self, VALUE key, VALUE val)
477{
478 struct weakmap *w;
479 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
480
481 VALUE pair[2] = { key, val };
482
483 st_update(w->table, (st_data_t)pair, wmap_aset_replace, (st_data_t)pair);
484
485 RB_OBJ_WRITTEN(self, Qundef, key);
486 RB_OBJ_WRITTEN(self, Qundef, val);
487
488 return Qnil;
489}
490
491/* Retrieves a weakly referenced object with the given key */
492static VALUE
493wmap_lookup(VALUE self, VALUE key)
494{
495 RUBY_ASSERT(wmap_live_p(key));
496
497 struct weakmap *w;
498 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
499
500 st_data_t data;
501 if (!st_lookup(w->table, (st_data_t)&key, &data)) return Qundef;
502
503 if (!wmap_live_p(*(VALUE *)data)) return Qundef;
504
505 return *(VALUE *)data;
506}
507
508/*
509 * call-seq:
510 * map[key] -> value
511 *
512 * Returns the value associated with the given +key+ if found.
513 *
514 * If +key+ is not found, returns +nil+.
515 */
516static VALUE
517wmap_aref(VALUE self, VALUE key)
518{
519 VALUE obj = wmap_lookup(self, key);
520 return !UNDEF_P(obj) ? obj : Qnil;
521}
522
523/*
524 * call-seq:
525 * map.delete(key) -> value or nil
526 * map.delete(key) {|key| ... } -> object
527 *
528 * Deletes the entry for the given +key+ and returns its associated value.
529 *
530 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
531 * m = ObjectSpace::WeakMap.new
532 * key = "foo"
533 * m[key] = 1
534 * m.delete(key) # => 1
535 * m[key] # => nil
536 *
537 * If no block is given and +key+ is not found, returns +nil+.
538 *
539 * If a block is given and +key+ is found, ignores the block,
540 * deletes the entry, and returns the associated value:
541 * m = ObjectSpace::WeakMap.new
542 * key = "foo"
543 * m[key] = 2
544 * m.delete(key) { |key| raise 'Will never happen'} # => 2
545 *
546 * If a block is given and +key+ is not found,
547 * yields the +key+ to the block and returns the block's return value:
548 * m = ObjectSpace::WeakMap.new
549 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
550 */
551static VALUE
552wmap_delete(VALUE self, VALUE key)
553{
554 struct weakmap *w;
555 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
556
557 VALUE orig_key = key;
558 st_data_t orig_key_data = (st_data_t)&orig_key;
559 st_data_t orig_val_data;
560 if (st_delete(w->table, &orig_key_data, &orig_val_data)) {
561 VALUE orig_val = *(VALUE *)orig_val_data;
562
563 rb_gc_remove_weak(self, (VALUE *)orig_key_data);
564 rb_gc_remove_weak(self, (VALUE *)orig_val_data);
565
566 struct weakmap_entry *entry = (struct weakmap_entry *)orig_key_data;
567 ruby_sized_xfree(entry, sizeof(struct weakmap_entry));
568
569 if (wmap_live_p(orig_val)) {
570 return orig_val;
571 }
572 }
573
574 if (rb_block_given_p()) {
575 return rb_yield(key);
576 }
577 else {
578 return Qnil;
579 }
580}
581
582/*
583 * call-seq:
584 * map.key?(key) -> true or false
585 *
586 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
587 */
588static VALUE
589wmap_has_key(VALUE self, VALUE key)
590{
591 return RBOOL(!UNDEF_P(wmap_lookup(self, key)));
592}
593
594/*
595 * call-seq:
596 * map.size -> number
597 *
598 * Returns the number of referenced objects
599 */
600static VALUE
601wmap_size(VALUE self)
602{
603 struct weakmap *w;
604 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
605
606 st_index_t n = st_table_size(w->table);
607
608#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
609 return ULONG2NUM(n);
610#else
611 return ULL2NUM(n);
612#endif
613}
614
615/* ===== WeakKeyMap =====
616 *
617 * WeakKeyMap contains one ST table which contains a pointer to the object as
618 * the key and the object as the value. This means that the key is of the type
619 * `VALUE *` while the value is of the type `VALUE`.
620 *
621 * The object is not directly stored as keys in the table because
622 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
623 * when the object is reclaimed. Using a pointer into the ST table entry is not
624 * safe because the pointer can change when the ST table is resized.
625 *
626 * WeakKeyMap hashes and compares using the `#hash` and `#==` methods of the
627 * object, respectively.
628 *
629 * During GC and while iterating, reclaimed entries (i.e. the key points to
630 * `Qundef`) are removed from the ST table.
631 */
632
634 st_table *table;
635};
636
637static int
638wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t data)
639{
640 VALUE **dead_entry = (VALUE **)data;
641 ruby_sized_xfree(*dead_entry, sizeof(VALUE));
642 *dead_entry = NULL;
643
644 VALUE *key_ptr = (VALUE *)key;
645
646 if (wmap_live_p(*key_ptr)) {
647 rb_gc_mark_weak(key_ptr);
648 rb_gc_mark_movable((VALUE)val_obj);
649
650 return ST_CONTINUE;
651 }
652 else {
653 *dead_entry = key_ptr;
654
655 return ST_DELETE;
656 }
657}
658
659static void
660wkmap_mark(void *ptr)
661{
662 struct weakkeymap *w = ptr;
663 if (w->table) {
664 VALUE *dead_entry = NULL;
665 st_foreach(w->table, wkmap_mark_table_i, (st_data_t)&dead_entry);
666 if (dead_entry != NULL) {
667 ruby_sized_xfree(dead_entry, sizeof(VALUE));
668 }
669 }
670}
671
672static int
673wkmap_free_table_i(st_data_t key, st_data_t _val, st_data_t _arg)
674{
675 ruby_sized_xfree((VALUE *)key, sizeof(VALUE));
676 return ST_CONTINUE;
677}
678
679static void
680wkmap_free(void *ptr)
681{
682 struct weakkeymap *w = ptr;
683
684 st_foreach(w->table, wkmap_free_table_i, 0);
685 st_free_table(w->table);
686}
687
688static size_t
689wkmap_memsize(const void *ptr)
690{
691 const struct weakkeymap *w = ptr;
692
693 size_t size = 0;
694 if (w->table) {
695 size += st_memsize(w->table);
696 /* Each key of the table takes sizeof(VALUE) in size. */
697 size += st_table_size(w->table) * sizeof(VALUE);
698 }
699
700 return size;
701}
702
703static int
704wkmap_compact_table_i(st_data_t key, st_data_t val_obj, st_data_t data, int _error)
705{
706 VALUE **dead_entry = (VALUE **)data;
707 ruby_sized_xfree(*dead_entry, sizeof(VALUE));
708 *dead_entry = NULL;
709
710 VALUE *key_ptr = (VALUE *)key;
711
712 if (wmap_live_p(*key_ptr)) {
713 if (*key_ptr != rb_gc_location(*key_ptr) || val_obj != rb_gc_location(val_obj)) {
714 return ST_REPLACE;
715 }
716
717 return ST_CONTINUE;
718 }
719 else {
720 *dead_entry = key_ptr;
721
722 return ST_DELETE;
723 }
724}
725
726static int
727wkmap_compact_table_replace(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t _data, int existing)
728{
729 RUBY_ASSERT(existing);
730
731 *(VALUE *)*key_ptr = rb_gc_location(*(VALUE *)*key_ptr);
732 *val_ptr = (st_data_t)rb_gc_location((VALUE)*val_ptr);
733
734 return ST_CONTINUE;
735}
736
737static void
738wkmap_compact(void *ptr)
739{
740 struct weakkeymap *w = ptr;
741
742 if (w->table) {
743 VALUE *dead_entry = NULL;
744 st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)&dead_entry);
745 if (dead_entry != NULL) {
746 ruby_sized_xfree(dead_entry, sizeof(VALUE));
747 }
748 }
749}
750
751static const rb_data_type_t weakkeymap_type = {
752 "weakkeymap",
753 {
754 wkmap_mark,
755 wkmap_free,
756 wkmap_memsize,
757 wkmap_compact,
758 },
759 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
760};
761
762static int
763wkmap_cmp(st_data_t x, st_data_t y)
764{
765 VALUE x_obj = *(VALUE *)x;
766 VALUE y_obj = *(VALUE *)y;
767
768 if (wmap_live_p(x_obj) && wmap_live_p(y_obj)) {
769 return rb_any_cmp(x_obj, y_obj);
770 }
771 else {
772 /* If one of the objects is dead, then they cannot be the same. */
773 return 1;
774 }
775}
776
777static st_index_t
778wkmap_hash(st_data_t n)
779{
780 VALUE obj = *(VALUE *)n;
781 RUBY_ASSERT(wmap_live_p(obj));
782
783 return rb_any_hash(obj);
784}
785
786static const struct st_hash_type wkmap_hash_type = {
787 wkmap_cmp,
788 wkmap_hash,
789};
790
791static VALUE
792wkmap_allocate(VALUE klass)
793{
794 struct weakkeymap *w;
795 VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &weakkeymap_type, w);
796 w->table = st_init_table(&wkmap_hash_type);
797 return obj;
798}
799
800static VALUE
801wkmap_lookup(VALUE self, VALUE key)
802{
803 struct weakkeymap *w;
804 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
805
806 st_data_t data;
807 if (!st_lookup(w->table, (st_data_t)&key, &data)) return Qundef;
808
809 return (VALUE)data;
810}
811
812/*
813 * call-seq:
814 * map[key] -> value
815 *
816 * Returns the value associated with the given +key+ if found.
817 *
818 * If +key+ is not found, returns +nil+.
819 */
820static VALUE
821wkmap_aref(VALUE self, VALUE key)
822{
823 VALUE obj = wkmap_lookup(self, key);
824 return !UNDEF_P(obj) ? obj : Qnil;
825}
826
828 VALUE new_key;
829 VALUE new_val;
830};
831
832static int
833wkmap_aset_replace(st_data_t *key, st_data_t *val, st_data_t data_args, int existing)
834{
835 struct wkmap_aset_args *args = (struct wkmap_aset_args *)data_args;
836
837 if (!existing) {
838 *key = (st_data_t)xmalloc(sizeof(VALUE));
839 }
840
841 *(VALUE *)*key = args->new_key;
842 *val = (st_data_t)args->new_val;
843
844 return ST_CONTINUE;
845}
846
847/*
848 * call-seq:
849 * map[key] = value -> value
850 *
851 * Associates the given +value+ with the given +key+
852 *
853 * The reference to +key+ is weak, so when there is no other reference
854 * to +key+ it may be garbage collected.
855 *
856 * If the given +key+ exists, replaces its value with the given +value+;
857 * the ordering is not affected
858 */
859static VALUE
860wkmap_aset(VALUE self, VALUE key, VALUE val)
861{
862 struct weakkeymap *w;
863 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
864
865 if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) {
866 rb_raise(rb_eArgError, "WeakKeyMap keys must be garbage collectable");
868 }
869
870 struct wkmap_aset_args args = {
871 .new_key = key,
872 .new_val = val,
873 };
874
875 st_update(w->table, (st_data_t)&key, wkmap_aset_replace, (st_data_t)&args);
876
877 RB_OBJ_WRITTEN(self, Qundef, key);
878 RB_OBJ_WRITTEN(self, Qundef, val);
879
880 return val;
881}
882
883/*
884 * call-seq:
885 * map.delete(key) -> value or nil
886 * map.delete(key) {|key| ... } -> object
887 *
888 * Deletes the entry for the given +key+ and returns its associated value.
889 *
890 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
891 * m = ObjectSpace::WeakKeyMap.new
892 * key = "foo" # to hold reference to the key
893 * m[key] = 1
894 * m.delete("foo") # => 1
895 * m["foo"] # => nil
896 *
897 * If no block given and +key+ is not found, returns +nil+.
898 *
899 * If a block is given and +key+ is found, ignores the block,
900 * deletes the entry, and returns the associated value:
901 * m = ObjectSpace::WeakKeyMap.new
902 * key = "foo" # to hold reference to the key
903 * m[key] = 2
904 * m.delete("foo") { |key| raise 'Will never happen'} # => 2
905 *
906 * If a block is given and +key+ is not found,
907 * yields the +key+ to the block and returns the block's return value:
908 * m = ObjectSpace::WeakKeyMap.new
909 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
910 */
911
912static VALUE
913wkmap_delete(VALUE self, VALUE key)
914{
915 struct weakkeymap *w;
916 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
917
918 VALUE orig_key = key;
919 st_data_t orig_key_data = (st_data_t)&orig_key;
920 st_data_t orig_val_data;
921 if (st_delete(w->table, &orig_key_data, &orig_val_data)) {
922 VALUE orig_val = (VALUE)orig_val_data;
923
924 rb_gc_remove_weak(self, (VALUE *)orig_key_data);
925
926 ruby_sized_xfree((VALUE *)orig_key_data, sizeof(VALUE));
927
928 return orig_val;
929 }
930
931 if (rb_block_given_p()) {
932 return rb_yield(key);
933 }
934 else {
935 return Qnil;
936 }
937}
938
939/*
940 * call-seq:
941 * map.getkey(key) -> existing_key or nil
942 *
943 * Returns the existing equal key if it exists, otherwise returns +nil+.
944 *
945 * This might be useful for implementing caches, so that only one copy of
946 * some object would be used everywhere in the program:
947 *
948 * value = {amount: 1, currency: 'USD'}
949 *
950 * # Now if we put this object in a cache:
951 * cache = ObjectSpace::WeakKeyMap.new
952 * cache[value] = true
953 *
954 * # ...we can always extract from there and use the same object:
955 * copy = cache.getkey({amount: 1, currency: 'USD'})
956 * copy.object_id == value.object_id #=> true
957 */
958static VALUE
959wkmap_getkey(VALUE self, VALUE key)
960{
961 struct weakkeymap *w;
962 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
963
964 st_data_t orig_key;
965 if (!st_get_key(w->table, (st_data_t)&key, &orig_key)) return Qnil;
966
967 return *(VALUE *)orig_key;
968}
969
970/*
971 * call-seq:
972 * map.key?(key) -> true or false
973 *
974 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
975 */
976static VALUE
977wkmap_has_key(VALUE self, VALUE key)
978{
979 return RBOOL(!UNDEF_P(wkmap_lookup(self, key)));
980}
981
982static int
983wkmap_clear_i(st_data_t key, st_data_t val, st_data_t data)
984{
985 VALUE self = (VALUE)data;
986
987 /* This WeakKeyMap may have already been marked, so we need to remove the
988 * keys to prevent a use-after-free. */
989 rb_gc_remove_weak(self, (VALUE *)key);
990 return wkmap_free_table_i(key, val, 0);
991}
992
993/*
994 * call-seq:
995 * map.clear -> self
996 *
997 * Removes all map entries; returns +self+.
998 */
999static VALUE
1000wkmap_clear(VALUE self)
1001{
1002 struct weakkeymap *w;
1003 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
1004
1005 st_foreach(w->table, wkmap_clear_i, (st_data_t)self);
1006 st_clear(w->table);
1007
1008 return self;
1009}
1010
1011/*
1012 * call-seq:
1013 * map.inspect -> new_string
1014 *
1015 * Returns a new String containing informations about the map:
1016 *
1017 * m = ObjectSpace::WeakKeyMap.new
1018 * m[key] = value
1019 * m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
1020 *
1021 */
1022static VALUE
1023wkmap_inspect(VALUE self)
1024{
1025 struct weakkeymap *w;
1026 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
1027
1028 st_index_t n = st_table_size(w->table);
1029
1030#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
1031 const char * format = "#<%"PRIsVALUE":%p size=%lu>";
1032#else
1033 const char * format = "#<%"PRIsVALUE":%p size=%llu>";
1034#endif
1035
1036 VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n);
1037 return str;
1038}
1039
1040/*
1041 * Document-class: ObjectSpace::WeakMap
1042 *
1043 * An ObjectSpace::WeakMap is a key-value map that holds weak references
1044 * to its keys and values, so they can be garbage-collected when there are
1045 * no more references left.
1046 *
1047 * Keys in the map are compared by identity.
1048 *
1049 * m = ObjectSpace::WeakMap.new
1050 * key1 = "foo"
1051 * val1 = Object.new
1052 * m[key1] = val1
1053 *
1054 * key2 = "bar"
1055 * val2 = Object.new
1056 * m[key2] = val2
1057 *
1058 * m[key1] #=> #<Object:0x0...>
1059 * m[key2] #=> #<Object:0x0...>
1060 *
1061 * val1 = nil # remove the other reference to value
1062 * GC.start
1063 *
1064 * m[key1] #=> nil
1065 * m.keys #=> ["bar"]
1066 *
1067 * key2 = nil # remove the other reference to key
1068 * GC.start
1069 *
1070 * m[key2] #=> nil
1071 * m.keys #=> []
1072 *
1073 * (Note that GC.start is used here only for demonstrational purposes and might
1074 * not always lead to demonstrated results.)
1075 *
1076 *
1077 * See also ObjectSpace::WeakKeyMap map class, which compares keys by value,
1078 * and holds weak references only to the keys.
1079 */
1080
1081/*
1082 * Document-class: ObjectSpace::WeakKeyMap
1083 *
1084 * An ObjectSpace::WeakKeyMap is a key-value map that holds weak references
1085 * to its keys, so they can be garbage collected when there is no more references.
1086 *
1087 * Unlike ObjectSpace::WeakMap:
1088 *
1089 * * references to values are _strong_, so they aren't garbage collected while
1090 * they are in the map;
1091 * * keys are compared by value (using Object#eql?), not by identity;
1092 * * only garbage-collectable objects can be used as keys.
1093 *
1094 * map = ObjectSpace::WeakKeyMap.new
1095 * val = Time.new(2023, 12, 7)
1096 * key = "name"
1097 * map[key] = val
1098 *
1099 * # Value is fetched by equality: the instance of string "name" is
1100 * # different here, but it is equal to the key
1101 * map["name"] #=> 2023-12-07 00:00:00 +0200
1102 *
1103 * val = nil
1104 * GC.start
1105 * # There are no more references to `val`, yet the pair isn't
1106 * # garbage-collected.
1107 * map["name"] #=> 2023-12-07 00:00:00 +0200
1108 *
1109 * key = nil
1110 * GC.start
1111 * # There are no more references to `key`, key and value are
1112 * # garbage-collected.
1113 * map["name"] #=> nil
1114 *
1115 * (Note that GC.start is used here only for demonstrational purposes and might
1116 * not always lead to demonstrated results.)
1117 *
1118 * The collection is especially useful for implementing caches of lightweight value
1119 * objects, so that only one copy of each value representation would be stored in
1120 * memory, but the copies that aren't used would be garbage-collected.
1121 *
1122 * CACHE = ObjectSpace::WeakKeyMap
1123 *
1124 * def make_value(**)
1125 * val = ValueObject.new(**)
1126 * if (existing = @cache.getkey(val))
1127 * # if the object with this value exists, we return it
1128 * existing
1129 * else
1130 * # otherwise, put it in the cache
1131 * @cache[val] = true
1132 * val
1133 * end
1134 * end
1135 *
1136 * This will result in +make_value+ returning the same object for same set of attributes
1137 * always, but the values that aren't needed anymore wouldn't be sitting in the cache forever.
1138 */
1139
1140void
1141Init_WeakMap(void)
1142{
1143 VALUE rb_mObjectSpace = rb_define_module("ObjectSpace");
1144
1145 VALUE rb_cWeakMap = rb_define_class_under(rb_mObjectSpace, "WeakMap", rb_cObject);
1146 rb_define_alloc_func(rb_cWeakMap, wmap_allocate);
1147 rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2);
1148 rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1);
1149 rb_define_method(rb_cWeakMap, "delete", wmap_delete, 1);
1150 rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1);
1151 rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1);
1152 rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1);
1153 rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0);
1154 rb_define_method(rb_cWeakMap, "each", wmap_each, 0);
1155 rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0);
1156 rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0);
1157 rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0);
1158 rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0);
1159 rb_define_method(rb_cWeakMap, "values", wmap_values, 0);
1160 rb_define_method(rb_cWeakMap, "size", wmap_size, 0);
1161 rb_define_method(rb_cWeakMap, "length", wmap_size, 0);
1162 rb_include_module(rb_cWeakMap, rb_mEnumerable);
1163
1164 VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjectSpace, "WeakKeyMap", rb_cObject);
1165 rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate);
1166 rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2);
1167 rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1);
1168 rb_define_method(rb_cWeakKeyMap, "delete", wkmap_delete, 1);
1169 rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1);
1170 rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1);
1171 rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0);
1172 rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0);
1173}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
void rb_include_module(VALUE klass, VALUE module)
Includes a module to a class.
Definition class.c:1685
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
Defines a class under the namespace of outer.
Definition class.c:1509
VALUE rb_define_module(const char *name)
Defines a top-level module.
Definition class.c:1591
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1010
#define Qundef
Old name of RUBY_Qundef.
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h:1684
#define T_FLOAT
Old name of RUBY_T_FLOAT.
Definition value_type.h:64
#define SPECIAL_CONST_P
Old name of RB_SPECIAL_CONST_P.
#define ULONG2NUM
Old name of RB_ULONG2NUM.
Definition long.h:60
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
#define CLASS_OF
Old name of rb_class_of.
Definition globals.h:205
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
#define FL_ABLE
Old name of RB_FL_ABLE.
Definition fl_type.h:121
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define Qnil
Old name of RUBY_Qnil.
#define SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
VALUE rb_any_to_s(VALUE obj)
Generates a textual representation of the given object.
Definition object.c:675
VALUE rb_mEnumerable
Enumerable module.
Definition enum.c:27
VALUE rb_inspect(VALUE obj)
Generates a human-readable textual representation of the given object.
Definition object.c:686
#define RB_OBJ_WRITTEN(old, oldv, young)
Identical to RB_OBJ_WRITE(), except it doesn't write any values, but only a WB declaration.
Definition gc.h:615
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3799
VALUE rb_class_name(VALUE obj)
Queries the name of the given object's class.
Definition variable.c:500
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
VALUE rb_yield_values(int n,...)
Identical to rb_yield(), except it takes variadic number of parameters and pass them to the block.
Definition vm_eval.c:1395
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1372
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
int st_foreach(st_table *q, int_type *w, st_data_t e)
Iteration over the given table.
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:649
struct rb_data_type_struct rb_data_type_t
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:205
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:508
#define _(args)
This was a transition path from K&R to ANSI.
Definition stdarg.h:35
Definition st.h:79
Definition weakmap.c:32
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376