Handle mutating of array passed to Set.new during iteration
authorJeremy Evans <[email protected]>
Sat, 3 May 2025 18:20:23 +0000 (3 11:20 -0700)
committerJeremy Evans <[email protected]>
Sat, 3 May 2025 19:10:57 +0000 (4 04:10 +0900)
This avoids a heap-use-after-free.

Fixes [Bug #21306]

set.c
test/ruby/test_set.rb

diff --git a/set.c b/set.c
index 0f72a8e..6fb04d8 100644 (file)
--- a/set.c
+++ b/set.c
@@ -494,18 +494,13 @@ set_i_initialize(int argc, VALUE *argv, VALUE set)
 
     if (argc > 0 && (other = argv[0]) != Qnil) {
         if (RB_TYPE_P(other, T_ARRAY)) {
-            long len = RARRAY_LEN(other);
-            if (RARRAY_LEN(other) != 0) {
-                set_table *into = RSET_TABLE(set);
-                VALUE key;
-                int block_given = rb_block_given_p();
-                RARRAY_PTR_USE(other, ptr, {
-                    for(; len > 0; len--, ptr++) {
-                        key = *ptr;
-                        if (block_given) key = rb_yield(key);
-                        set_table_insert_wb(into, set, key, NULL);
-                    }
-                });
+            long i;
+            int block_given = rb_block_given_p();
+            set_table *into = RSET_TABLE(set);
+            for (i=0; i<RARRAY_LEN(other); i++) {
+                VALUE key = RARRAY_AREF(other, i);
+                if (block_given) key = rb_yield(key);
+                set_table_insert_wb(into, set, key, NULL);
             }
         }
         else {
index 225b7da..2bb7858 100644 (file)
@@ -643,6 +643,11 @@ class TC_Set < Test::Unit::TestCase
     assert_equal([o], Set.new.merge(a).to_a)
   end
 
+  def test_initialize_mutating_array_bug_21306
+    a = (1..100).to_a
+    assert_equal(Set[0], Set.new(a){a.clear; 0})
+  end
+
   def test_subtract
     set = Set[1,2,3]