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