From 0f408602cbcdc33faf4e2b545a7b62718e401807 Mon Sep 17 00:00:00 2001 From: Kunshan Wang Date: Thu, 17 Jul 2025 22:44:02 +0800 Subject: [PATCH 1/7] Fix missing increment of deleted_entries When `rb_concurrent_set_foreach_with_replace` deletes entries from a concurrent set, it should increment the `deleted_entries` field, too. --- concurrent_set.c | 1 + 1 file changed, 1 insertion(+) diff --git a/concurrent_set.c b/concurrent_set.c index ec1e7ef3073c13..ffbd028a2c0294 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -402,6 +402,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key return; case ST_DELETE: set->entries[i].key = CONCURRENT_SET_DELETED; + set->deleted_entries++; break; } break; From a1403fb7cbd1fe0df97c932be9814c86081783dc Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 21 Jul 2025 17:13:44 -0700 Subject: [PATCH 2/7] Interpolated strings must not be frozen Strings concatenated with backslash may end up being frozen when they shouldn't be. This commit fixes the issue. It required a change upstream in Prism, but also a change to the Prism compiler in CRuby. https://github.com/ruby/prism/pull/3606 [Bug #21187] --- prism/prism.c | 4 ++++ prism_compile.c | 12 ++++++------ test/prism/result/static_literals_test.rb | 5 +++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 85647020d832cb..a40e0ebeb0c821 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5279,6 +5279,10 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ switch (PM_NODE_TYPE(part)) { case PM_STRING_NODE: + // If inner string is not frozen, clear flags for this string + if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) { + CLEAR_FLAGS(node); + } part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE); break; case PM_INTERPOLATED_STRING_NODE: diff --git a/prism_compile.c b/prism_compile.c index a38f790b3b2903..17db5e58c881f9 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -556,7 +556,7 @@ parse_regexp_concat(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node); static int -pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding) +pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding, bool mutable_result) { int stack_size = 0; size_t parts_size = parts->size; @@ -666,7 +666,7 @@ pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const if (RTEST(current_string)) { current_string = rb_fstring(current_string); - if (stack_size == 0 && interpolated) { + if (stack_size == 0 && (interpolated || mutable_result)) { PUSH_INSN1(ret, current_location, putstring, current_string); } else { @@ -690,7 +690,7 @@ pm_compile_regexp_dynamic(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_ rb_encoding *explicit_regexp_encoding = parse_regexp_encoding(scope_node, node); rb_encoding *implicit_regexp_encoding = explicit_regexp_encoding != NULL ? explicit_regexp_encoding : scope_node->encoding; - int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding); + int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding, false); PUSH_INSN2(ret, *node_location, toregexp, INT2FIX(parse_regexp_flags(node) & 0xFF), INT2FIX(length)); } @@ -9637,7 +9637,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } else { const pm_interpolated_string_node_t *cast = (const pm_interpolated_string_node_t *) node; - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, !PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN)); if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); if (popped) PUSH_INSN(ret, location, pop); } @@ -9648,7 +9648,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // :"foo #{bar}" // ^^^^^^^^^^^^^ const pm_interpolated_symbol_node_t *cast = (const pm_interpolated_symbol_node_t *) node; - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, false); if (length > 1) { PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); @@ -9670,7 +9670,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_INSN(ret, location, putself); - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL, false); if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); PUSH_SEND_WITH_FLAG(ret, location, idBackquote, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); diff --git a/test/prism/result/static_literals_test.rb b/test/prism/result/static_literals_test.rb index dcfc692897cd66..cc070279169aba 100644 --- a/test/prism/result/static_literals_test.rb +++ b/test/prism/result/static_literals_test.rb @@ -4,6 +4,11 @@ module Prism class StaticLiteralsTest < TestCase + def test_concatenanted_string_literal_is_not_static + node = Prism.parse_statement("'a' 'b'") + refute_predicate node, :static_literal? + end + def test_static_literals assert_warning("1") assert_warning("0xA", "10", "10") From 973e6770d51ce8f1ff31fca8b28335811ce16ee2 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 18 Jul 2025 00:02:53 -0700 Subject: [PATCH 3/7] Fix TSAN data race in gc_start objspace->flags.immediate_sweep shares the same word as objspace->flags.during_incremental_marking. So in gc_start we need to assign it after gc_enter() so that we hold the VM lock and have issued a barrier, as rb_gc_impl_writebarrier is reading objspace->flags.during_incremental_marking. --- gc/default/default.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 384b3f10f05771..de3bee15223b19 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6316,9 +6316,6 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) { unsigned int do_full_mark = !!(reason & GPR_FLAG_FULL_MARK); - /* reason may be clobbered, later, so keep set immediate_sweep here */ - objspace->flags.immediate_sweep = !!(reason & GPR_FLAG_IMMEDIATE_SWEEP); - if (!rb_darray_size(objspace->heap_pages.sorted)) return TRUE; /* heap is not ready */ if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */ @@ -6329,6 +6326,9 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) unsigned int lock_lev; gc_enter(objspace, gc_enter_event_start, &lock_lev); + /* reason may be clobbered, later, so keep set immediate_sweep here */ + objspace->flags.immediate_sweep = !!(reason & GPR_FLAG_IMMEDIATE_SWEEP); + #if RGENGC_CHECK_MODE >= 2 gc_verify_internal_consistency(objspace); #endif From 41ec0f5b978cba26deb7c099f580cf02ecbde42e Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 18 Jul 2025 14:51:20 -0700 Subject: [PATCH 4/7] Update misc/tsan_suppressions.txt * Add gc_enable/disable to TSAN suppressions * Remove deleted methods from tsan suppressions * Remove TSAN errors we've fixed * Add another two inline cache suppressions * Improve comments --- misc/tsan_suppressions.txt | 39 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/misc/tsan_suppressions.txt b/misc/tsan_suppressions.txt index e46f133a9e5623..a34e040913d9dd 100644 --- a/misc/tsan_suppressions.txt +++ b/misc/tsan_suppressions.txt @@ -37,22 +37,16 @@ race_top:rb_ec_vm_lock_rec race_top:vm_lock_enter race_top:vm_locked -# Ractors -race:ractor_take -race:ractor_register_take -race:ractor_check_take_basket -race:ractor_selector__wait - # vm->ractor.sched.grq_cnt++ race_top:ractor_sched_enq race_top:ractor_sched_deq -# Using VM lock instead of rb_native_mutex_unlock? +# Race between vm_remove_ractor writing ractor count and +# native_thread_check_and_create_shared reading it during thread creation. +# The write happens when a ractor thread exits, the read happens when +# checking if new shared threads need to be created. race:vm_remove_ractor -# cr->sync.wait.wakeup_status -race_top:rb_ractor_sched_sleep - # th->sched.finished at end of co_start race_top:rb_thread_sched_mark_zombies @@ -62,9 +56,6 @@ race_top:thread_sched_wait_events # At thread start race_top:rb_ractor_set_current_ec_ -# Possible deadlock between Ractor lock and UBF lock -deadlock:ractor_sleep_interrupt - # TSan reports a lock-order-inversion between thread_sched_lock_ and this lock. # It's unclear if that can cause a deadlock since the lock is on self deadlock:ractor_lock_self @@ -76,16 +67,22 @@ deadlock:rb_ractor_sched_barrier_start # RVALUE_AGE_SET manipulates flag bits on objects which may be accessed in Ractors race_top:RVALUE_AGE_SET -# Inline caches +# Inline caches and call cache updates +# Multiple threads can race when updating shared call caches during method lookups +# and argument forwarding. These races involve reading/writing cd->cc fields. race_top:vm_cc_call_set race_top:vm_cc_class_check race_top:vm_search_cc race_top:vm_search_method_slowpath0 race_top:rb_vm_opt_getconstant_path race_top:vm_ic_attr_index_set +race:vm_ic_update +race:vm_caller_setup_fwd_args -# Shapes have problems with RCLASS_MAX_IV_COUNT and RCLASS_VARIATION_COUNT -# which are probably benign +# Race in shape_get_next where multiple threads simultaneously access and modify +# RCLASS_MAX_IV_COUNT and RCLASS_VARIATION_COUNT fields in class objects. +# One thread reads the field while another thread calls RCLASS_SET_MAX_IV_COUNT. +# This happens during instance variable shape transitions in multi-threaded code. race:shape_get_next # Non-atomic reads/writes @@ -99,16 +96,10 @@ race:rb_ec_cleanup # timer thread race:after_fork_ruby -# object_id races -race:object_id - # Sets objspace->flags.dont_incremental while writebarrier may be running race_top:objspace_each_exec race_top:objspace_each_objects_ensure -# Ractor autoload -race:rb_ractor_autoload_load - # Non-atomic lazy initialized static variable race_top:rbimpl_intern_const @@ -123,3 +114,7 @@ race:rb_tracepoint_enable # We walk the machine stack looking for markable objects, a thread with the GVL # released could by mutating the stack with non-Ruby-objects race:rb_gc_mark_machine_context + +# GC enable/disable flag modifications race with object allocation flag reads +race_top:rb_gc_impl_gc_disable +race_top:rb_gc_impl_gc_enable From 7bb48f87f35d4d99312c94e3982e2f483675a777 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Mon, 7 Jul 2025 18:07:47 +0200 Subject: [PATCH 5/7] [ruby/openssl] ssl: add SSLSocket#sigalg, #peer_sigalg, #group These methods are useful to test post-quantum cryptography (PQC) cases. https://github.com/ruby/openssl/commit/434ef74452 --- ext/openssl/extconf.rb | 6 ++++ ext/openssl/ossl_ssl.c | 69 ++++++++++++++++++++++++++++++++++++++++ test/openssl/test_ssl.rb | 25 +++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index afbed10b54bd06..8aac52ef47e546 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -156,9 +156,15 @@ def find_openssl_library have_func("EVP_PKEY_eq(NULL, NULL)", evp_h) have_func("EVP_PKEY_dup(NULL)", evp_h) +# added in 3.2.0 +have_func("SSL_get0_group_name(NULL)", ssl_h) + # added in 3.4.0 have_func("TS_VERIFY_CTX_set0_certs(NULL, NULL)", ts_h) +# added in 3.5.0 +have_func("SSL_get0_peer_signature_name(NULL, NULL)", ssl_h) + Logging::message "=== Checking done. ===\n" # Append flags from environment variables. diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index b5872f588125c2..f3da26517ff4a7 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -2644,6 +2644,68 @@ ossl_ssl_tmp_key(VALUE self) return Qnil; return ossl_pkey_new(key); } + +#ifdef HAVE_SSL_GET0_PEER_SIGNATURE_NAME +/* + * call-seq: + * ssl.sigalg => String or nil + * + * Returns the signature algorithm name, the IANA name of the signature scheme + * used by the local to sign the TLS handshake. + */ +static VALUE +ossl_ssl_get_sigalg(VALUE self) +{ + SSL *ssl; + const char *name; + + GetSSL(self, ssl); + if (!SSL_get0_signature_name(ssl, &name)) + return Qnil; + return rb_str_new_cstr(name); +} + +/* + * call-seq: + * ssl.peer_sigalg => String or nil + * + * Returns the signature algorithm name, the IANA name of the signature scheme + * used by the peer to sign the TLS handshake. + */ +static VALUE +ossl_ssl_get_peer_sigalg(VALUE self) +{ + SSL *ssl; + const char *name; + + GetSSL(self, ssl); + if (!SSL_get0_peer_signature_name(ssl, &name)) + return Qnil; + return rb_str_new_cstr(name); +} +#endif + +#ifdef HAVE_SSL_GET0_GROUP_NAME +/* + * call-seq: + * ssl.group => String or nil + * + * Returns the name of the group that was used for the key agreement of the + * current TLS session establishment. + */ +static VALUE +ossl_ssl_get_group(VALUE self) +{ + SSL *ssl; + const char *name; + + GetSSL(self, ssl); + if (!(name = SSL_get0_group_name(ssl))) + return Qnil; + return rb_str_new_cstr(name); +} +#endif + #endif /* !defined(OPENSSL_NO_SOCK) */ void @@ -3067,6 +3129,13 @@ Init_ossl_ssl(void) # ifdef OSSL_USE_NEXTPROTONEG rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); # endif +#ifdef HAVE_SSL_GET0_PEER_SIGNATURE_NAME + rb_define_method(cSSLSocket, "sigalg", ossl_ssl_get_sigalg, 0); + rb_define_method(cSSLSocket, "peer_sigalg", ossl_ssl_get_peer_sigalg, 0); +#endif +#ifdef HAVE_SSL_GET0_GROUP_NAME + rb_define_method(cSSLSocket, "group", ossl_ssl_get_group, 0); +#endif rb_define_const(mSSL, "VERIFY_NONE", INT2NUM(SSL_VERIFY_NONE)); rb_define_const(mSSL, "VERIFY_PEER", INT2NUM(SSL_VERIFY_PEER)); diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index febac061564dfd..05f29507650749 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -2041,6 +2041,27 @@ def test_client_sigalgs end end + def test_get_sigalg + # SSL_get0_signature_name() not supported + # SSL_get0_peer_signature_name() not supported + return unless openssl?(3, 5, 0) + + server_proc = -> (ctx, ssl) { + assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) + assert_nil(ssl.peer_sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(server_proc: server_proc) do |port| + cli_ctx = OpenSSL::SSL::SSLContext.new + server_connect(port, cli_ctx) do |ssl| + assert_nil(ssl.sigalg) + assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + end + end + end + def test_connect_works_when_setting_dh_callback_to_nil omit "AWS-LC does not support DHE ciphersuites" if aws_lc? @@ -2088,6 +2109,8 @@ def test_set_groups_tls12 server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs + # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. + assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -2127,6 +2150,8 @@ def test_set_groups_tls13 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version + # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. + assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } From 02aee1b724e102453d34af8341dd52a85e49b1b0 Mon Sep 17 00:00:00 2001 From: Allison Cretel Date: Fri, 18 Jul 2025 11:46:56 -0400 Subject: [PATCH 6/7] Change `TESTOPS` to `TESTOPTS` --- doc/contributing/making_changes_to_stdlibs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributing/making_changes_to_stdlibs.md b/doc/contributing/making_changes_to_stdlibs.md index 721ba842896a25..2156a61e3948a3 100644 --- a/doc/contributing/making_changes_to_stdlibs.md +++ b/doc/contributing/making_changes_to_stdlibs.md @@ -45,5 +45,5 @@ bundle exec rake test TEST="test/test_foo.rb" To run a single test case: ```shell -bundle exec rake test TEST="test/test_foo.rb" TESTOPS="--name=/test_mytest/" +bundle exec rake test TEST="test/test_foo.rb" TESTOPTS="--name=/test_mytest/" ``` From 45e65f55bc55497b620ba6e27c85bbe07185f1c4 Mon Sep 17 00:00:00 2001 From: Allison Cretel Date: Fri, 18 Jul 2025 11:47:16 -0400 Subject: [PATCH 7/7] Fix typos in `documentation_guide.md` --- doc/contributing/documentation_guide.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index a913aa10865506..8a73543e6c9076 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -61,7 +61,7 @@ Use your judgment about what the user needs to know. Use only US-ASCII-compatible characters in a C source file. (If you use other characters, the Ruby CI will gently let you know.) -If want to put ASCII-incompatible characters into the documentation +If you want to put ASCII-incompatible characters into the documentation for a C-coded class, module, or method, there are workarounds involving new files `doc/*.rdoc`: @@ -74,7 +74,7 @@ involving new files `doc/*.rdoc`: class Foo; end ``` -- Similarly, for module `Bar` (defined in file `bar.c`, +- Similarly, for module `Bar` (defined in file `bar.c`), create file `doc/bar.rdoc`, declare `module Bar; end`, and place the module documentation above that declaration: @@ -284,7 +284,7 @@ Use a full URL-based link for: - A link in standard library documentation to documentation in a different standard library package. -Doing so ensures that the link will valid even when the package documentation +Doing so ensures that the link will be valid even when the package documentation is built independently (separately from the core documentation). The link should lead to a target in https://docs.ruby-lang.org/en/master/. @@ -484,7 +484,7 @@ Return types: - If the method can return multiple types, use +object+. - If the method returns the receiver, use +self+. - If the method returns an object of the same class, - prefix `new_` if an only if the object is not +self+; + prefix `new_` if and only if the object is not +self+; example: `new_array`. Aliases: @@ -588,7 +588,7 @@ mention `Hash#fetch` as a related method, and `Hash#merge` might mention `Hash#merge!` as a related method. - Consider which methods may be related - to the current method, and if you think the reader would benefit it, + to the current method, and if you think the reader would benefit from it, at the end of the method documentation, add a line starting with "Related: " (e.g. "Related: #fetch."). - Don't list more than three related methods. @@ -597,7 +597,7 @@ mention `Hash#fetch` as a related method, and `Hash#merge` might mention - Consider adding: - A phrase suggesting how the related method is similar to, - or different from,the current method. + or different from, the current method. See an example at Time#getutc. - Example code that illustrates the similarities and differences. See examples at Time#ctime, Time#inspect, Time#to_s.