From 9c85a94f5f1b553ccac3e7f232087424d1854f97 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Sep 2025 18:42:18 +0900 Subject: [PATCH 01/26] vcs.rb: Drop support for ruby older than 2.6 --- tool/lib/vcs.rb | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 2c019d81fd421f..45b84b303503ad 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -51,23 +51,9 @@ def popen(*args) end using DebugPOpen module DebugSystem - def system(*args) + def system(*args, exception: true, **opts) VCS.dump(args, "args: ") if $DEBUG - exception = false - opts = Hash.try_convert(args[-1]) - if RUBY_VERSION >= "2.6" - unless opts - opts = {} - args << opts - end - exception = opts.fetch(:exception) {opts[:exception] = true} - elsif opts - exception = opts.delete(:exception) {true} - args.pop if opts.empty? - end - ret = super(*args) - raise "Command failed with status (#$?): #{args[0]}" if exception and !ret - ret + super(*args, exception: exception, **opts) end end From 08e7b5f2bdef276c7ea65fa3b01f53c1062acf8d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 16 Sep 2025 19:29:08 +0900 Subject: [PATCH 02/26] [ruby/openssl] Revert "pkey: stop retrying after non-retryable error from OSSL_DECODER" This reverts commit https://github.com/ruby/openssl/commit/5347880c6eb0 and https://github.com/ruby/openssl/commit/985ba27d6339. These commits attempted to stop processing after the first relevant PEM block, whether it is successful or not, when the input contains multiple keys. It turned out that it cannot be reliably determined using the OSSL_DECODER API. There is an edge case where OSSL_DECODER_from_bio() reports "unsupported" even though the input actually contains an error: https://redirect.github.com/ruby/openssl/pull/931#discussion_r2347813807 Revert the changes for now and keep the existing behavior, as partial support does not seem worth the added complexity. https://github.com/ruby/openssl/commit/319cd4952a --- ext/openssl/ossl_pkey.c | 41 +++++++++++++-------------------------- test/openssl/test_pkey.rb | 11 ----------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index ee6a7a988f59f0..0fed03332fc31e 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -83,15 +83,13 @@ ossl_pkey_wrap(EVP_PKEY *pkey) # include static EVP_PKEY * -ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, - int *retryable) +ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass) { void *ppass = (void *)pass; OSSL_DECODER_CTX *dctx; EVP_PKEY *pkey = NULL; int pos = 0, pos2; - *retryable = 0; dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, input_type, NULL, NULL, selection, NULL, NULL); if (!dctx) @@ -102,25 +100,17 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, goto out; while (1) { if (OSSL_DECODER_from_bio(dctx, bio) == 1) + goto out; + if (BIO_eof(bio)) break; - // Error queue may not be populated in OpenSSL < 3.0.11 and < 3.1.3 - // https://github.com/openssl/openssl/pull/21603 - unsigned long err = ERR_peek_error(); - if (err && ERR_GET_REASON(err) != ERR_R_UNSUPPORTED) - break; - if (BIO_eof(bio) == 1) { - *retryable = 1; - break; - } pos2 = BIO_tell(bio); - if (pos2 < 0 || pos2 <= pos) { - *retryable = 1; + if (pos2 < 0 || pos2 <= pos) break; - } ossl_clear_error(); pos = pos2; } out: + OSSL_BIO_reset(bio); OSSL_DECODER_CTX_free(dctx); return pkey; } @@ -128,6 +118,7 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, EVP_PKEY * ossl_pkey_read_generic(BIO *bio, VALUE pass) { + EVP_PKEY *pkey = NULL; /* First check DER, then check PEM. */ const char *input_types[] = {"DER", "PEM"}; int input_type_num = (int)(sizeof(input_types) / sizeof(char *)); @@ -176,22 +167,18 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass) EVP_PKEY_PUBLIC_KEY }; int selection_num = (int)(sizeof(selections) / sizeof(int)); + int i, j; - for (int i = 0; i < input_type_num; i++) { - for (int j = 0; j < selection_num; j++) { - if (i || j) { - ossl_clear_error(); - BIO_reset(bio); + for (i = 0; i < input_type_num; i++) { + for (j = 0; j < selection_num; j++) { + pkey = ossl_pkey_read(bio, input_types[i], selections[j], pass); + if (pkey) { + goto out; } - - int retryable; - EVP_PKEY *pkey = ossl_pkey_read(bio, input_types[i], selections[j], - pass, &retryable); - if (pkey || !retryable) - return pkey; } } - return NULL; + out: + return pkey; } #else EVP_PKEY * diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 24a595333b4ea3..8066c4dc190fa9 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -124,17 +124,6 @@ def test_s_read_passphrase } } assert_equal(1, called) - - # Incorrect passphrase returned by the block. The input contains two PEM - # blocks. - called = 0 - assert_raise(OpenSSL::PKey::PKeyError) { - OpenSSL::PKey.read(encrypted_pem + encrypted_pem) { - called += 1 - "incorrect_passphrase" - } - } - assert_equal(1, called) end def test_s_read_passphrase_tty From ce1ed871b72dbf427efa748bac586dc2dca14a52 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Sep 2025 20:43:19 +0900 Subject: [PATCH 03/26] Revert "Already git 2.32 is expected in doc/contributing/building_ruby.md" This reverts commit fbc7e935761d892ba6c031256ccbb914963e4ce1. Failures with "GCC 8" and "clang 12". ``` /github/workspace/src/tool/lib/vcs.rb:57:in `system': Command failed with status (pid 7385 exit 128): ["git", "config", "--global", "--add", "safe.directory", "/github/workspace/src"] (RuntimeError) ``` --- configure.ac | 5 +++-- win32/Makefile.sub | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 6d91d764456e36..b035e1688a8461 100644 --- a/configure.ac +++ b/configure.ac @@ -113,9 +113,10 @@ AC_ARG_WITH(git, { test x"$HAVE_GIT" = xyes && command -v "$GIT" > /dev/null && - # see Dependencies in doc/contributing/building_ruby.md + # `git -C`: 1.8.5 + # `git log --no-show-signature`: 2.10.0 AS_CASE([`$GIT -C . --version 2> /dev/null | sed 's/.* //'`], - [0.*|1.*|2.@<:@0-9@:>@.*|2.@<:@12@:>@@<:@0-9@:>@.*|2.3@<:@01@:>@.*], [false], + [0.*|1.*|2.@<:@0-9@:>@.*], [false], [true]) } || HAVE_GIT=no GIT=never-use AC_SUBST(GIT) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 917c407a006124..81a33277afb161 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -526,7 +526,7 @@ HAVE_GIT = no !else if [for /f "tokens=3" %I in ('git --version') do @(\ for /f "delims=. tokens=1-2" %I in ("%I") do @(\ if %I lss 2 (exit 1) else if %I gtr 2 (exit 0) \ - else if %J lss 32 (exit 1) else (exit 0)\ + else if %J lss 10 (exit 1) else (exit 0)\ )\ )] HAVE_GIT = no From 0f3c6ca480d3dab95b355392658972323f890e7d Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Mon, 15 Sep 2025 20:53:55 +0300 Subject: [PATCH 04/26] [ruby/openssl] c_rehash: fix hash_name output for small hashes The hash lookup is done by 8-character hash. https://github.com/ruby/openssl/commit/fedb57255c --- sample/openssl/c_rehash.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample/openssl/c_rehash.rb b/sample/openssl/c_rehash.rb index de4b66e9022b88..8b005bbb849ac3 100644 --- a/sample/openssl/c_rehash.rb +++ b/sample/openssl/c_rehash.rb @@ -156,7 +156,7 @@ def path(filename) end def hash_name(name) - sprintf("%x", name.hash) + sprintf("%08x", name.hash) end def fingerprint(der) From a1f39b4b807a5412181ca3f1bf87e7c7d2d9f542 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 18 Sep 2025 00:32:47 +1200 Subject: [PATCH 05/26] [ruby/openssl] Add AuthTagError exception for AEAD authentication failures (https://github.com/ruby/openssl/pull/939) * Add AuthTagError exception for AEAD authentication failures - Add OpenSSL::Cipher::AuthTagError as a subclass of CipherError - Raise AuthTagError specifically for AEAD cipher authentication tag verification failures - Enhanced error messages: 'AEAD authentication tag verification failed' for auth failures - Precise detection: Only EVP_CipherFinal_ex failures in AEAD ciphers raise AuthTagError - All other errors (key setup, IV setup, update failures, etc.) still raise CipherError - Comprehensive test coverage for GCM/CCM modes and error inheritance - Fully backwards compatible: AuthTagError < CipherError https://github.com/ruby/openssl/commit/9663b09040 --- ext/openssl/ossl_cipher.c | 15 +++++++++++++-- test/openssl/test_cipher.rb | 13 +++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 07e651335d53e1..bec634ae11a1f0 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -32,6 +32,7 @@ */ static VALUE cCipher; static VALUE eCipherError; +static VALUE eAuthTagError; static ID id_auth_tag_len, id_key_set; static VALUE ossl_cipher_alloc(VALUE klass); @@ -415,8 +416,17 @@ ossl_cipher_final(VALUE self) GetCipher(self, ctx); str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx)); - if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) - ossl_raise(eCipherError, NULL); + if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) { + /* For AEAD ciphers, this is likely an authentication failure */ + if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) { + /* For AEAD ciphers, EVP_CipherFinal_ex failures are authentication tag verification failures */ + ossl_raise(eAuthTagError, "AEAD authentication tag verification failed"); + } + else { + /* For non-AEAD ciphers */ + ossl_raise(eCipherError, "cipher final failed"); + } + } assert(out_len <= RSTRING_LEN(str)); rb_str_set_len(str, out_len); @@ -1027,6 +1037,7 @@ Init_ossl_cipher(void) */ cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject); eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError); + eAuthTagError = rb_define_class_under(cCipher, "AuthTagError", eCipherError); rb_define_alloc_func(cCipher, ossl_cipher_alloc); rb_define_method(cCipher, "initialize_copy", ossl_cipher_copy, 1); diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index cd0b3dcb4450f6..10858b353559e1 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -182,6 +182,10 @@ def test_update_raise_if_key_not_set end end + def test_auth_tag_error_inheritance + assert_equal OpenSSL::Cipher::CipherError, OpenSSL::Cipher::AuthTagError.superclass + end + def test_authenticated cipher = OpenSSL::Cipher.new('aes-128-gcm') assert_predicate(cipher, :authenticated?) @@ -212,7 +216,8 @@ def test_aes_ccm cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad) assert_equal pt, cipher.update(ct) << cipher.final - # wrong tag is rejected + # wrong tag is rejected - in CCM, authentication happens during update, but + # we consider this a general CipherError since update failures can have various causes tag2 = tag.dup tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff) cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad) @@ -265,19 +270,19 @@ def test_aes_gcm tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff) cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad) cipher.update(ct) - assert_raise(OpenSSL::Cipher::CipherError) { cipher.final } + assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final } # wrong aad is rejected aad2 = aad[0..-2] << aad[-1].succ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2) cipher.update(ct) - assert_raise(OpenSSL::Cipher::CipherError) { cipher.final } + assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final } # wrong ciphertext is rejected ct2 = ct[0..-2] << ct[-1].succ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad) cipher.update(ct2) - assert_raise(OpenSSL::Cipher::CipherError) { cipher.final } + assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final } end def test_aes_gcm_variable_iv_len From 73b08ff423fb1f1779d5e17f47b0fb5478021151 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 29 Aug 2025 16:57:12 +0100 Subject: [PATCH 06/26] [ruby/openssl] Fix test_ssl.rb in FIPS. test_post_connect_check_with_anon_ciphers: test_tmp_dh_callback: test_tmp_dh: DH missing the q value on unknown named parameters (ciphers) is not FIPS-approved, according to the FIPS-186-4 APPENDIX B: Key Pair Generation - B.1.1 Key Pair Generation Using Extra Random Bits, the inputs p, q, and g are required. However, TLS doesn't send q. https://csrc.nist.gov/pubs/fips/186-4/final OpenSSL has a special workaround to recover the missing "q" value for known named parameters, which is the reason why other tests that use the default parameters in `lib/openssl/ssl.rb` are working. Note that the test_post_connect_check_with_anon_ciphers test got the following error on `OpenSSL.debug = true` in FIPS. ``` /home/jaruga/var/git/ruby/openssl/lib/openssl/ssl.rb:551: warning: error on stack: error:0A0C0103:SSL routines:tls_construct_server_key_exchange:internal error ``` test_get_ephemeral_key: kRSA (PKCS1-v1_5 padding) is not allowed in FIPS according to the NIST SP 800-131A Rev. 2 - 6 Key Agreement and Key Transport Using RSA - Table 5: Approval Status for the RSA-based Key Agreement and Key Transport Schemes - PKCS1-v1_5 padding - Disallowed after 2023 https://csrc.nist.gov/pubs/sp/800/131/a/r2/final Note that the test_get_ephemeral_key test got the following error on `OpenSSL.debug = true` in FIPS. ``` test/openssl/test_ssl.rb:2326: warning: error on stack: error:1C8000A8:Provider routines:rsa_encrypt:invalid padding mode ``` https://github.com/ruby/openssl/commit/ac3559e51e --- test/openssl/test_ssl.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index fc19f4d9459d86..7abe2c6df5d330 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -685,6 +685,8 @@ def test_sslctx_set_params end def test_post_connect_check_with_anon_ciphers + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? ctx_proc = -> ctx { @@ -1747,6 +1749,9 @@ def test_sync_close_without_connect end def test_get_ephemeral_key + # kRSA is not FIPS-approved. + omit_on_fips + # kRSA ctx_proc1 = proc { |ctx| ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION @@ -1863,6 +1868,8 @@ def test_fallback_scsv end def test_tmp_dh_callback + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? dh = Fixtures.pkey("dh-1") @@ -2131,6 +2138,8 @@ def test_connect_works_when_setting_dh_callback_to_nil end def test_tmp_dh + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? dh = Fixtures.pkey("dh-1") From 74075617b17ab80db74a5e7c1c8515601a8ce6f4 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Sep 2025 15:05:29 +0200 Subject: [PATCH 07/26] Remove setting v1, v2, v3 when creating a new object Setting v1, v2, v3 when we allocate an object assumes that we always allocate 40 byte objects. By removing v1, v2, v3, we can make the base slot size another size. --- gc.c | 28 +++++++++++++++++++++------- gc/default/default.c | 16 ++-------------- gc/gc_impl.h | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/gc.c b/gc.c index 0433da2e67c282..aabb732ce66659 100644 --- a/gc.c +++ b/gc.c @@ -620,7 +620,7 @@ typedef struct gc_function_map { void (*stress_set)(void *objspace_ptr, VALUE flag); VALUE (*stress_get)(void *objspace_ptr); // Object allocation - VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size); + VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); size_t (*obj_slot_size)(VALUE obj); size_t (*heap_id_for_size)(void *objspace_ptr, size_t size); bool (*size_allocatable_p)(size_t size); @@ -984,9 +984,9 @@ gc_validate_pc(void) } static inline VALUE -newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t size) +newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, bool wb_protected, size_t size) { - VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, v1, v2, v3, wb_protected, size); + VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, wb_protected, size); gc_validate_pc(); @@ -1029,14 +1029,14 @@ VALUE rb_wb_unprotected_newobj_of(VALUE klass, VALUE flags, size_t size) { GC_ASSERT((flags & FL_WB_PROTECTED) == 0); - return newobj_of(GET_RACTOR(), klass, flags, 0, 0, 0, FALSE, size); + return newobj_of(GET_RACTOR(), klass, flags, FALSE, size); } VALUE rb_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, size_t size) { GC_ASSERT((flags & FL_WB_PROTECTED) == 0); - return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, 0, 0, 0, TRUE, size); + return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, TRUE, size); } #define UNEXPECTED_NODE(func) \ @@ -1057,7 +1057,14 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU { RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1); if (klass) rb_data_object_check(klass); - return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, !dmark, sizeof(struct RTypedData)); + VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA, !dmark, sizeof(struct RTypedData)); + + struct RData *data = (struct RData *)obj; + data->dmark = dmark; + data->dfree = dfree; + data->data = datap; + + return obj; } VALUE @@ -1074,7 +1081,14 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; - return newobj_of(GET_RACTOR(), klass, T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, 0, ((VALUE)type) | typed_flag, (VALUE)datap, wb_protected, size); + VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, wb_protected, size); + + struct RTypedData *data = (struct RTypedData *)obj; + data->fields_obj = 0; + *(VALUE *)&data->type = ((VALUE)type) | typed_flag; + data->data = datap; + + return obj; } VALUE diff --git a/gc/default/default.c b/gc/default/default.c index 7264fb799627eb..b7d37149734c40 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2096,16 +2096,6 @@ heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap) GC_ASSERT(heap->free_pages != NULL); } -static inline VALUE -newobj_fill(VALUE obj, VALUE v1, VALUE v2, VALUE v3) -{ - VALUE *p = (VALUE *)(obj + sizeof(struct RBasic)); - p[0] = v1; - p[1] = v2; - p[2] = v3; - return obj; -} - #if GC_DEBUG static inline const char* rb_gc_impl_source_location_cstr(int *ptr) @@ -2146,8 +2136,6 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, #endif #if RGENGC_CHECK_MODE - newobj_fill(obj, 0, 0, 0); - int lev = RB_GC_VM_LOCK_NO_BARRIER(); { check_rvalue_consistency(objspace, obj); @@ -2469,7 +2457,7 @@ newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace } VALUE -rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size) +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { VALUE obj; rb_objspace_t *objspace = objspace_ptr; @@ -2500,7 +2488,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags newobj_slowpath_wb_unprotected(klass, flags, objspace, cache, heap_idx); } - return newobj_fill(obj, v1, v2, v3); + return obj; } static int diff --git a/gc/gc_impl.h b/gc/gc_impl.h index b461f3a3054cd2..3250483639e775 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -55,7 +55,7 @@ GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr); GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr); GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash); // Object allocation -GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size); +GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj); GC_IMPL_FN size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size); GC_IMPL_FN bool rb_gc_impl_size_allocatable_p(size_t size); From 42b6ec0149fbf97d79ddd4d957cecc0fc08f2701 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Sep 2025 16:26:58 +0200 Subject: [PATCH 08/26] Clear memory for newly allocated iseq --- iseq.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iseq.h b/iseq.h index 1cecc6960d0b5a..c7f091a0b4c2df 100644 --- a/iseq.h +++ b/iseq.h @@ -175,7 +175,12 @@ ISEQ_COMPILE_DATA_CLEAR(rb_iseq_t *iseq) static inline rb_iseq_t * iseq_imemo_alloc(void) { - return IMEMO_NEW(rb_iseq_t, imemo_iseq, 0); + rb_iseq_t *iseq = IMEMO_NEW(rb_iseq_t, imemo_iseq, 0); + + // Clear out the whole iseq except for the flags. + memset((char *)iseq + sizeof(VALUE), 0, sizeof(rb_iseq_t) - sizeof(VALUE)); + + return iseq; } VALUE rb_iseq_ibf_dump(const rb_iseq_t *iseq, VALUE opt); From 3f3497558146c46350b740db2e71fdf28d30c204 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Sep 2025 16:28:45 +0200 Subject: [PATCH 09/26] Clear out memory for newly alloated string --- string.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/string.c b/string.c index 834641cfdbf0c1..807d86f04deda2 100644 --- a/string.c +++ b/string.c @@ -1006,6 +1006,8 @@ str_alloc_embed(VALUE klass, size_t capa) NEWOBJ_OF(str, struct RString, klass, T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size, 0); + str->len = 0; + return (VALUE)str; } @@ -1015,6 +1017,10 @@ str_alloc_heap(VALUE klass) NEWOBJ_OF(str, struct RString, klass, T_STRING | STR_NOEMBED | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), sizeof(struct RString), 0); + str->len = 0; + str->as.heap.aux.capa = 0; + str->as.heap.ptr = NULL; + return (VALUE)str; } @@ -1866,6 +1872,8 @@ ec_str_alloc_embed(struct rb_execution_context_struct *ec, VALUE klass, size_t c NEWOBJ_OF(str, struct RString, klass, T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size, ec); + str->len = 0; + return (VALUE)str; } @@ -1875,6 +1883,9 @@ ec_str_alloc_heap(struct rb_execution_context_struct *ec, VALUE klass) NEWOBJ_OF(str, struct RString, klass, T_STRING | STR_NOEMBED | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), sizeof(struct RString), ec); + str->as.heap.aux.capa = 0; + str->as.heap.ptr = NULL; + return (VALUE)str; } From cfc5c565038d3ed00a739135911e6188929c729a Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Sep 2025 17:20:05 +0200 Subject: [PATCH 10/26] Clear out memory for newly allocated tmpbuf --- imemo.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/imemo.c b/imemo.c index 74eae5678f3532..10ac960095675f 100644 --- a/imemo.c +++ b/imemo.c @@ -54,6 +54,9 @@ rb_imemo_tmpbuf_new(void) VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); NEWOBJ_OF(obj, rb_imemo_tmpbuf_t, 0, flags, sizeof(rb_imemo_tmpbuf_t), NULL); + obj->ptr = NULL; + obj->cnt = 0; + return (VALUE)obj; } From 2d57e9e2a185c68d6f1cd3f067f19c23fe5f3fc1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Sep 2025 17:29:27 +0200 Subject: [PATCH 11/26] Clear out memory for newly allocated array --- array.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/array.c b/array.c index d9dff28ad95ebd..2ba95bef186dae 100644 --- a/array.c +++ b/array.c @@ -695,6 +695,11 @@ ary_alloc_heap(VALUE klass) NEWOBJ_OF(ary, struct RArray, klass, T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), sizeof(struct RArray), 0); + + ary->as.heap.len = 0; + ary->as.heap.aux.capa = 0; + ary->as.heap.ptr = NULL; + return (VALUE)ary; } @@ -808,6 +813,11 @@ ec_ary_alloc_heap(rb_execution_context_t *ec, VALUE klass) NEWOBJ_OF(ary, struct RArray, klass, T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), sizeof(struct RArray), ec); + + ary->as.heap.len = 0; + ary->as.heap.aux.capa = 0; + ary->as.heap.ptr = NULL; + return (VALUE)ary; } From f7ddf7b30e118d67e3062cd363f72d9bba109671 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Sep 2025 17:29:41 +0200 Subject: [PATCH 12/26] Clear object_id for newly allocated class --- class.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/class.c b/class.c index 472095617c1e8a..b15f9f5ae7258d 100644 --- a/class.c +++ b/class.c @@ -682,6 +682,8 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size, 0); + obj->object_id = 0; + memset(RCLASS_EXT_PRIME(obj), 0, sizeof(rb_classext_t)); /* ZALLOC From 3b2f698432350dadcb87fd4dfb11e1b2c31c36c7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 12 Sep 2025 15:00:38 -0400 Subject: [PATCH 13/26] Null terminate newly created embedded strings --- string.c | 1 + 1 file changed, 1 insertion(+) diff --git a/string.c b/string.c index 807d86f04deda2..624c61896f01f7 100644 --- a/string.c +++ b/string.c @@ -1007,6 +1007,7 @@ str_alloc_embed(VALUE klass, size_t capa) T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size, 0); str->len = 0; + str->as.embed.ary[0] = 0; return (VALUE)str; } From a456e79e06faf36caac20d9b33e92a98613c9d07 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 12 Sep 2025 15:01:02 -0400 Subject: [PATCH 14/26] Clear out memory for newly allocated structs --- struct.c | 1 + 1 file changed, 1 insertion(+) diff --git a/struct.c b/struct.c index c53e68b3da7f67..2076f5709e304d 100644 --- a/struct.c +++ b/struct.c @@ -837,6 +837,7 @@ struct_alloc(VALUE klass) st->as.heap.ptr = struct_heap_alloc((VALUE)st, n); rb_mem_clear((VALUE *)st->as.heap.ptr, n); st->as.heap.len = n; + st->as.heap.fields_obj = 0; return (VALUE)st; } From 262838f2e09beca72b695a90bd1d2bfd9e924170 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 16 Sep 2025 10:42:53 -0400 Subject: [PATCH 15/26] Fill more of the slot with garbage --- gc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gc.c b/gc.c index aabb732ce66659..5a18afa4a71417 100644 --- a/gc.c +++ b/gc.c @@ -1016,9 +1016,9 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, bool wb_protected, size_t s # endif memset( - (void *)(obj + RVALUE_SIZE), + (void *)(obj + sizeof(struct RBasic)), GC_DEBUG_SLOT_FILL_SPECIAL_VALUE, - rb_gc_obj_slot_size(obj) - RVALUE_SIZE + rb_gc_obj_slot_size(obj) - sizeof(struct RBasic) ); #endif From a38b2753ec9627f9441dbb6bc3b9ceb1473ff804 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 16 Sep 2025 10:47:30 -0400 Subject: [PATCH 16/26] Update rb_gc_impl_new_obj in mmtk.c --- gc/mmtk/mmtk.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 7b7f76eb1a564e..9dd3129e01664e 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -598,7 +598,7 @@ rb_gc_impl_config_set(void *objspace_ptr, VALUE hash) // Object allocation VALUE -rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size) +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { #define MMTK_ALLOCATION_SEMANTICS_DEFAULT 0 struct objspace *objspace = objspace_ptr; @@ -622,9 +622,6 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags alloc_obj[-1] = alloc_size; alloc_obj[0] = flags; alloc_obj[1] = klass; - if (alloc_size > 16) alloc_obj[2] = v1; - if (alloc_size > 24) alloc_obj[3] = v2; - if (alloc_size > 32) alloc_obj[4] = v3; mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size + 8, MMTK_ALLOCATION_SEMANTICS_DEFAULT); From 58ece00dd50ffe92ba142fb51b0adc7d5a356752 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 16 Sep 2025 13:24:53 -0400 Subject: [PATCH 17/26] Clear out memory for rb_big_new --- bignum.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bignum.c b/bignum.c index 2e135caf20fb09..054b6c1cc9afe3 100644 --- a/bignum.c +++ b/bignum.c @@ -3055,7 +3055,9 @@ bignew_1(VALUE klass, size_t len, int sign) VALUE rb_big_new(size_t len, int sign) { - return bignew(len, sign != 0); + VALUE obj = bignew(len, sign != 0); + memset(BIGNUM_DIGITS(obj), 0, len * sizeof(BDIGIT)); + return obj; } VALUE From dd69a75f2abfe280a62545a4f827bca4d2df1789 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Sep 2025 21:49:27 +0900 Subject: [PATCH 18/26] fetch-bundled_gems.rb: Fix extraneous newline Print a concatenated string instead of `print`ing multiple strings, since `$\` is set to `"\n"` because of `-l` command line option. --- tool/fetch-bundled_gems.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tool/fetch-bundled_gems.rb b/tool/fetch-bundled_gems.rb index e46c5bdc1c5ff0..127ea236f3d5e4 100755 --- a/tool/fetch-bundled_gems.rb +++ b/tool/fetch-bundled_gems.rb @@ -40,9 +40,8 @@ end checkout = %w"git -c advice.detachedHead=false checkout" -print %[checking out #{color.notice(c)} (v=#{color.info(v)}] -print %[, r=#{color.info(r)}] if r -puts ") ..." +info = %[, r=#{color.info(r)}] if r +puts "checking out #{color.notice(c)} (v=#{color.info(v)}#{info}) ..." unless system(*checkout, c, "--", chdir: n) abort if r or !system(*checkout, v, "--", chdir: n) end From b034a3df6872fd38adf924b9f9f74ac245302f95 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 17 Jan 2025 19:39:27 +0900 Subject: [PATCH 19/26] bundled_gems_spec.rb: Remove useless `map` `Dir.[]` returns list of strings, `map(&:to_s)` does nothing. --- spec/bundled_gems_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundled_gems_spec.rb b/spec/bundled_gems_spec.rb index c362881850f775..8014a48223c8cf 100644 --- a/spec/bundled_gems_spec.rb +++ b/spec/bundled_gems_spec.rb @@ -31,7 +31,7 @@ def self.ruby=(ruby) FileUtils.cp_r Spec::Path.pristine_system_gem_path, Spec::Path.system_gem_path FileUtils.mkdir_p Spec::Path.base_system_gem_path.join("gems") %w[sinatra rack tilt rack-protection rack-session rack-test mustermann base64 logger compact_index].each do |gem| - path = Dir[File.expand_path("../.bundle/gems/#{gem}-*", __dir__)].map(&:to_s).first + path, = Dir[File.expand_path("../.bundle/gems/#{gem}-*", __dir__)] FileUtils.cp_r path, Spec::Path.base_system_gem_path.join("gems") end From e758198846b7811f20e1c21aa971124fbb2fe103 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 12 Aug 2025 15:18:13 +0900 Subject: [PATCH 20/26] test-bundled-gems.rb: Allow wildcards in `BUNDLED_GEMS` --- tool/test-bundled-gems.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index c44b78d63b4bdf..e73a29c54bb6ae 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -19,7 +19,7 @@ allowed_failures = allowed_failures.split(',').concat(DEFAULT_ALLOWED_FAILURES).uniq.reject(&:empty?) # make test-bundled-gems BUNDLED_GEMS=gem1,gem2,gem3 -bundled_gems = ARGV.first || '' +bundled_gems = nil if (bundled_gems = ARGV.first&.split(","))&.empty? colorize = Colorize.new rake = File.realpath("../../.bundle/bin/rake", __FILE__) @@ -30,7 +30,7 @@ failed = [] File.foreach("#{gem_dir}/bundled_gems") do |line| next unless gem = line[/^[^\s\#]+/] - next unless bundled_gems.empty? || bundled_gems.split(",").include?(gem) + next if bundled_gems&.none? {|pat| File.fnmatch?(pat, gem)} next unless File.directory?("#{gem_dir}/src/#{gem}/test") test_command = "#{ruby} -C #{gem_dir}/src/#{gem} #{rake} test" From df2f462accc7cd927f3738b54cb92707c34a9f7c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 17 Sep 2025 11:50:46 -0400 Subject: [PATCH 21/26] Skip TestObjSpaceRactor#test_tracing_does_not_crash It crashes frequently on CI but I am not able to reproduce locally: https://ci.rvm.jp/results/trunk-random1@ruby-sp2-noble-docker/5954509 https://ci.rvm.jp/results/trunk-random0@ruby-sp2-noble-docker/5954501 --- test/objspace/test_ractor.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/objspace/test_ractor.rb b/test/objspace/test_ractor.rb index eb3044cda3c3c3..a14604c1e7c05f 100644 --- a/test/objspace/test_ractor.rb +++ b/test/objspace/test_ractor.rb @@ -2,6 +2,10 @@ class TestObjSpaceRactor < Test::Unit::TestCase def test_tracing_does_not_crash + # https://ci.rvm.jp/results/trunk-random1@ruby-sp2-noble-docker/5954509 + # https://ci.rvm.jp/results/trunk-random0@ruby-sp2-noble-docker/5954501 + omit "crashes frequently on CI but not able to reproduce locally" + assert_ractor(<<~RUBY, require: 'objspace') ObjectSpace.trace_object_allocations do r = Ractor.new do From c22d84a1211f139cbbb9b5fc00082e7b6a7c652e Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 17 Sep 2025 12:29:20 -0400 Subject: [PATCH 22/26] ZJIT: Revert documentation indent (#14580) --- zjit/src/profile.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index c53943a4577abb..f73138d4bdcc41 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -150,7 +150,8 @@ impl Flags { /// opt_send_without_block/opt_plus/... should store: /// * the class of the receiver, so we can do method lookup /// * the shape of the receiver, so we can optimize ivar lookup -/// with those two, pieces of information, we can also determine when an object is an immediate: +/// +/// with those two, pieces of information, we can also determine when an object is an immediate: /// * Integer + IS_IMMEDIATE == Fixnum /// * Float + IS_IMMEDIATE == Flonum /// * Symbol + IS_IMMEDIATE == StaticSymbol From 3c7a89721d53ba1cbe70a21cdf8a839b1aa16341 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:23:07 +0200 Subject: [PATCH 23/26] [ruby/prism] Reject `1 if foo = bar baz` and also `1 and foo = bar baz` This is a partial fix for https://github.com/ruby/prism/issues/3106 It still accepts `a = b c and 1` https://github.com/ruby/prism/commit/7a13d3535b --- prism/prism.c | 2 +- test/prism/errors/command_calls_33.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 test/prism/errors/command_calls_33.txt diff --git a/prism/prism.c b/prism/prism.c index 2e202c37456ea5..42a821e53584a9 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -20909,7 +20909,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding bool permitted = true; if (previous_binding_power != PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_USTAR)) permitted = false; - pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id, (uint16_t) (depth + 1)); + pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MODIFIER, diag_id, (uint16_t) (depth + 1)); if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE); parse_assignment_value_local(parser, value); diff --git a/test/prism/errors/command_calls_33.txt b/test/prism/errors/command_calls_33.txt new file mode 100644 index 00000000000000..13e3b35c9e83b6 --- /dev/null +++ b/test/prism/errors/command_calls_33.txt @@ -0,0 +1,6 @@ +1 if foo = bar baz + ^~~ unexpected local variable or method, expecting end-of-input + +1 and foo = bar baz + ^~~ unexpected local variable or method, expecting end-of-input + From 3fa1eb71151fbd8c970218e6df85c94e8048eef0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 17 Sep 2025 12:03:08 -0500 Subject: [PATCH 24/26] [ruby/erb] [DOC] Adds section 'Error Reporting' (https://github.com/ruby/erb/pull/75) https://github.com/ruby/erb/commit/8dc0eacaad --- lib/erb.rb | 78 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/lib/erb.rb b/lib/erb.rb index bb2baf089f739b..9c4cac3f10c988 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -531,6 +531,49 @@ # # => # # ``` # +# ## Error Reporting +# +# Consider this template (containing an error): +# +# ``` +# s = '<%= nosuch %>' +# template = ERB.new(s) +# ``` +# +# When \ERB reports an error, +# it includes a file name (if available) and a line number; +# the file name comes from method #filename, the line number from method #lineno. +# +# Initially, those values are `nil` and `0`, respectively; +# these initial values are reported as `'(erb)'` and `1`, respectively: +# +# ``` +# template.filename # => nil +# template.lineno # => 0 +# template.result +# (erb):1:in '
': undefined local variable or method 'nosuch' for main (NameError) +# ``` +# +# You can use methods #filename= and #lineno= to assign values +# that are more meaningful in your context: +# +# ``` +# template.filename = 't.txt' +# # => "t.txt" +# template.lineno = 555 +# # => 555 +# template.result +# t.txt:556:in '
': undefined local variable or method 'nosuch' for main (NameError) +# ``` +# +# You can use method #location= to set both values: +# +# ``` +# template.location = ['u.txt', 999] +# template.result +# u.txt:1000:in '
': undefined local variable or method 'nosuch' for main (NameError) +# ``` +# # ## Plain Text Example # # Here's a plain-text string; @@ -833,29 +876,32 @@ def make_compiler(trim_mode) # The encoding to eval attr_reader :encoding - # The optional _filename_ argument passed to Kernel#eval when the ERB code - # is run + # :markup: markdown + # + # Sets or returns the file name to be used in reporting errors; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting attr_accessor :filename - # The optional _lineno_ argument passed to Kernel#eval when the ERB code - # is run + # :markup: markdown + # + # Sets or returns the line number to be used in reporting errors; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting attr_accessor :lineno + # :markup: markdown # - # Sets optional filename and line number that will be used in ERB code - # evaluation and error reporting. See also #filename= and #lineno= - # - # erb = ERB.new('<%= some_x %>') - # erb.render - # # undefined local variable or method `some_x' - # # from (erb):1 + # :call-seq: + # location = [filename, lineno] => [filename, lineno] + # location = filename -> filename # - # erb.location = ['file.erb', 3] - # # All subsequent error reporting would use new location - # erb.render - # # undefined local variable or method `some_x' - # # from file.erb:4 + # Sets the values of #filename and, if given, #lineno; + # see [Error Reporting][error reporting]. # + # [error reporting]: rdoc-ref:ERB@Error+Reporting def location=((filename, lineno)) @filename = filename @lineno = lineno if lineno From 7ed1b24bf3a9400cdf67cc080414d56fa3cfe72d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 17:15:23 -0400 Subject: [PATCH 25/26] Unset RUBY_CRASH_REPORT in tests that crash on purpose --- test/-ext-/bug_reporter/test_bug_reporter.rb | 2 +- test/ruby/test_rubyoptions.rb | 1 + test/ruby/test_vm_dump.rb | 3 ++- tool/test/testunit/test_parallel.rb | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/test/-ext-/bug_reporter/test_bug_reporter.rb b/test/-ext-/bug_reporter/test_bug_reporter.rb index 1350675a48d3ed..83fdba22829e97 100644 --- a/test/-ext-/bug_reporter/test_bug_reporter.rb +++ b/test/-ext-/bug_reporter/test_bug_reporter.rb @@ -23,7 +23,7 @@ def test_bug_reporter_add # We want the printed description to match this process's RUBY_DESCRIPTION args.push("--yjit") if JITSupport.yjit_enabled? args.push("--zjit") if JITSupport.zjit_enabled? - args.unshift({"RUBY_ON_BUG" => nil}) + args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil}) stdin = "#{no_core}register_sample_bug_reporter(12345); Process.kill :SEGV, $$" assert_in_out_err(args, stdin, [], expected_stderr, encoding: "ASCII-8BIT") ensure diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 2c9f8534b19aad..2b7cf1c8999a4c 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -855,6 +855,7 @@ def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &b args.unshift("--yjit") if JITSupport.yjit_enabled? args.unshift("--zjit") if JITSupport.zjit_enabled? env.update({'RUBY_ON_BUG' => nil}) + env['RUBY_CRASH_REPORT'] ||= nil # default to not passing down parent setting # ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when # catching sigsegv; we don't expect that output, so suppress it. env.update({'ASAN_OPTIONS' => 'handle_segv=0'}) diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb index a3e7b69913b7d2..d183e033915cd7 100644 --- a/test/ruby/test_vm_dump.rb +++ b/test/ruby/test_vm_dump.rb @@ -5,6 +5,7 @@ class TestVMDump < Test::Unit::TestCase def assert_darwin_vm_dump_works(args, timeout=nil) + args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil}) assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300) end @@ -13,7 +14,7 @@ def test_darwin_invalid_call end def test_darwin_segv_in_syscall - assert_darwin_vm_dump_works('-e1.times{Process.kill :SEGV,$$}') + assert_darwin_vm_dump_works(['-e1.times{Process.kill :SEGV,$$}']) end def test_darwin_invalid_access diff --git a/tool/test/testunit/test_parallel.rb b/tool/test/testunit/test_parallel.rb index d87e0ed3271a35..adf7d62ecd581a 100644 --- a/tool/test/testunit/test_parallel.rb +++ b/tool/test/testunit/test_parallel.rb @@ -151,9 +151,9 @@ def test_quit end class TestParallel < Test::Unit::TestCase - def spawn_runner(*opt_args, jobs: "t1") + def spawn_runner(*opt_args, jobs: "t1", env: {}) @test_out, o = IO.pipe - @test_pid = spawn(*@__runner_options__[:ruby], TESTS+"/runner.rb", + @test_pid = spawn(env, *@__runner_options__[:ruby], TESTS+"/runner.rb", "--ruby", @__runner_options__[:ruby].join(" "), "-j", jobs, *opt_args, out: o, err: o) o.close @@ -214,7 +214,7 @@ def test_separate end def test_hungup - spawn_runner "--worker-timeout=1", "--retry", "test4test_hungup.rb" + spawn_runner("--worker-timeout=1", "--retry", "test4test_hungup.rb", env: {"RUBY_CRASH_REPORT"=>nil}) buf = ::TestParallel.timeout(TIMEOUT) {@test_out.read} assert_match(/^Retrying hung up testcases\.+$/, buf) assert_match(/^2 tests,.* 0 failures,/, buf) From 4d003eabec01a5c0be10e3c6bb7e641f9e04eeef Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 16:33:41 -0400 Subject: [PATCH 26/26] GitHub CI: JITs: Dump RUBY_CRASH_REPORT on failure We sometimes see silent crashes on CI such as . The default of dumping to stderr could be interacting poorly with the parallel workers, so let's use RUBY_CRASH_REPORT to hopefully get more information. --- .github/workflows/yjit-macos.yml | 10 ++++++++-- .github/workflows/yjit-ubuntu.yml | 11 +++++++++-- .github/workflows/zjit-macos.yml | 9 +++++++++ .github/workflows/zjit-ubuntu.yml | 9 +++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 53ffcbe217069e..b44187b0fb1cd8 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -115,8 +115,10 @@ jobs: ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' if: ${{ contains(matrix.configure, 'jit=dev') }} - - name: Enable YJIT through ENV - run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + - name: Set ENV for YJIT + run: | + echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV - name: Set test options for skipped tests run: | @@ -166,6 +168,10 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} + - if: ${{ failure() }} + continue-on-error: true + run: tail --verbose --lines=+1 rb_crash_*.txt + - uses: ./.github/actions/slack with: label: ${{ matrix.test_task }} ${{ matrix.configure }} ${{ matrix.yjit_opts }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 3ff3310a44a5a0..1fac6e1d9395ae 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -168,8 +168,10 @@ jobs: ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' if: ${{ contains(matrix.configure, 'jit=dev') }} - - name: Enable YJIT through ENV - run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + - name: Set ENV for YJIT + run: | + echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV # Check that the binary was built with YJIT - name: Check YJIT enabled @@ -208,6 +210,11 @@ jobs: LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} continue-on-error: ${{ matrix.continue-on-test_task || false }} + - name: Dump crash logs + if: ${{ failure() }} + continue-on-error: true + run: tail --verbose --lines=+1 rb_crash_*.txt + - uses: ./.github/actions/slack with: label: ${{ matrix.test_task }} ${{ matrix.configure }} diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 3da1080be96a09..987a26a62bf6f0 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -107,6 +107,10 @@ jobs: ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' if: ${{ contains(matrix.configure, 'jit=dev') }} + - name: Set ENV for ZJIT + run: | + echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV + - name: make ${{ matrix.test_task }} run: >- make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} @@ -122,6 +126,11 @@ jobs: TESTS: ${{ matrix.test_all_opts }} continue-on-error: ${{ matrix.continue-on-test_task || false }} + - name: Dump crash logs + if: ${{ failure() }} + continue-on-error: true + run: tail --verbose --lines=+1 rb_crash_*.txt + - uses: ./.github/actions/slack with: label: ${{ matrix.test_task }} ${{ matrix.configure }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 14b9eab0423105..c85a3799a006ca 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -147,6 +147,10 @@ jobs: run: ./miniruby --zjit -v | grep "+ZJIT" if: ${{ matrix.configure != '--disable-zjit' }} + - name: Set ENV for ZJIT + run: | + echo "RUBY_CRASH_REPORT=$(pwd)/rb_crash_%p.txt" >> $GITHUB_ENV + - name: make ${{ matrix.test_task }} run: >- make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} @@ -164,6 +168,11 @@ jobs: TESTS: ${{ matrix.test_all_opts }} continue-on-error: ${{ matrix.continue-on-test_task || false }} + - name: Dump crash logs + if: ${{ failure() }} + continue-on-error: true + run: tail --verbose --lines=+1 rb_crash_*.txt + - uses: ./.github/actions/slack with: label: ${{ matrix.test_task }} ${{ matrix.configure }}