Ruby: weakmap.c Source File

Ruby 3.3.5p100 (2024-09-03 revision ef084cc8f4958c1b6e4ead99136631bef6d8ddba)

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