From 0b264a25c6959bfa2bffe3484e5921fa5fd72088 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Sep 2025 23:24:28 +0900 Subject: [PATCH 001/104] ABI version is defined only in master --- .github/actions/capiext/action.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index e8ea87e61019d7..a43d0890ef1255 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -26,7 +26,10 @@ runs: run: | eval $(grep -e '^arch *=' -e '^ruby_version *=' -e '^DLEXT *=' Makefile | sed 's/ *= */=/') - key=capiexts-${arch}-${ruby_version} + case "${ruby_version}" in + *+*) key=capiexts-${arch}-${ruby_version};; + *) key=;; + esac echo key=$key >> $GITHUB_OUTPUT echo DLEXT=$DLEXT >> $GITHUB_OUTPUT working-directory: ${{ inputs.builddir }} @@ -37,6 +40,7 @@ runs: with: path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ key: ${{ steps.config.outputs.key }} + if: ${{ steps.config.outputs.key }} - name: Run test-spec with previous CAPI extension binaries shell: bash From 0dbf3c080b62ad83dbb2db78e442c24ab856454b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 11:02:30 +0900 Subject: [PATCH 002/104] Show annotation --- .github/actions/capiext/action.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index a43d0890ef1255..a41202c7a4053a 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -30,6 +30,7 @@ runs: *+*) key=capiexts-${arch}-${ruby_version};; *) key=;; esac + echo version=$ruby_version >> $GITHUB_OUTPUT echo key=$key >> $GITHUB_OUTPUT echo DLEXT=$DLEXT >> $GITHUB_OUTPUT working-directory: ${{ inputs.builddir }} @@ -43,6 +44,7 @@ runs: if: ${{ steps.config.outputs.key }} - name: Run test-spec with previous CAPI extension binaries + id: check shell: bash run: | touch spec/ruby/optional/capi/ext/*.$DLEXT @@ -52,3 +54,10 @@ runs: DLEXT: ${{ steps.config.outputs.DLEXT }} working-directory: ${{ inputs.builddir }} if: ${{ steps.cache.outputs.cache-hit }} + + - shell: bash + run: | + echo "::error::Change from ${prev} detected; bump up ABI version" + env: + prev: ${{ steps.config.outputs.version }} + if: ${{ always() && steps.check.outcome == 'failure' }} From ca70f442b8a49cd8fe6b62abbf067cd953d29664 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 11:28:59 +0900 Subject: [PATCH 003/104] Matrix for extra checks --- .github/workflows/macos.yml | 3 ++- .github/workflows/ubuntu.yml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d22ada675545bb..0b9d5d049d635e 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -45,6 +45,7 @@ jobs: os: macos-14 - test_task: check os: macos-15 + extra_checks: [capi] capi_check: capi - test_task: check os: macos-13 @@ -166,7 +167,7 @@ jobs: builddir: build env: RUBY_TESTOPTS: '-v --tty=no' - if: ${{ matrix.capi_check }} + if: ${{ contains(matrix.extra_checks, 'capi') }} - uses: ./.github/actions/slack with: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 5c8a072a16742c..6249418a9abafb 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -39,7 +39,7 @@ jobs: - test_task: test-bundled-gems - test_task: check os: ubuntu-24.04 - capi_check: capi + extra_checks: [capi] # ubuntu-24.04-arm jobs don't start on ruby/ruby as of 2025-09-04 #- test_task: check # os: ubuntu-24.04-arm @@ -160,7 +160,7 @@ jobs: make: '$SETARCH make' env: RUBY_TESTOPTS: '-v --tty=no' - if: ${{ matrix.capi_check }} + if: ${{ contains(matrix.extra_checks, 'capi') }} - uses: ./.github/actions/slack with: From 4131ace07ab604a39acd56de3597ea3e1f785c4e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Sep 2025 21:50:27 -0700 Subject: [PATCH 004/104] ZJIT, YJIT: Drop "// From xxx.h" comments in bindgen (#14519) --- yjit/bindgen/src/main.rs | 99 -------------------------------------- zjit/bindgen/src/main.rs | 100 --------------------------------------- 2 files changed, 199 deletions(-) diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 2fc85431e0439d..b62c637e1dbc4e 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -65,35 +65,24 @@ fn main() { // Import YARV bytecode instruction constants .allowlist_type("ruby_vminsn_type") - // From include/ruby/internal/special_consts.h .allowlist_type("ruby_special_consts") - - // From include/ruby/internal/intern/string.h .allowlist_function("rb_utf8_str_new") .allowlist_function("rb_str_buf_append") .allowlist_function("rb_str_dup") - - // From encindex.h .allowlist_type("ruby_preserved_encindex") - - // From include/ruby/ruby.h .allowlist_function("rb_class2name") // This struct is public to Ruby C extensions - // From include/ruby/internal/core/rbasic.h .allowlist_type("RBasic") - // From include/ruby/internal/core/rstring.h .allowlist_type("ruby_rstring_flags") - // From internal.h // This function prints info about a value and is useful for debugging .allowlist_function("rb_obj_info_dump") // For crashing .allowlist_function("rb_bug") - // From shape.h .allowlist_function("rb_obj_shape_id") .allowlist_function("rb_shape_id_offset") .allowlist_function("rb_shape_get_iv_index") @@ -103,36 +92,20 @@ fn main() { .allowlist_function("rb_yjit_shape_index") .allowlist_var("SHAPE_ID_NUM_BITS") .allowlist_var("SHAPE_ID_HAS_IVAR_MASK") - - // From ruby/internal/eval.h .allowlist_function("rb_funcall") - - // From ruby/internal/intern/object.h .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") - - // From ruby/internal/encoding/encoding.h .allowlist_type("ruby_encoding_consts") - - // From include/hash.h .allowlist_function("rb_hash_new") - - // From internal/hash.h .allowlist_function("rb_hash_new_with_size") .allowlist_function("rb_hash_resurrect") .allowlist_function("rb_hash_stlike_foreach") .allowlist_function("rb_to_hash_type") - - // From include/ruby/st.h .allowlist_type("st_retval") - - // From include/ruby/internal/intern/hash.h .allowlist_function("rb_hash_aset") .allowlist_function("rb_hash_aref") .allowlist_function("rb_hash_bulk_insert") .allowlist_function("rb_hash_stlike_lookup") - - // From include/ruby/internal/intern/array.h .allowlist_function("rb_ary_new_capa") .allowlist_function("rb_ary_store") .allowlist_function("rb_ary_resurrect") @@ -142,26 +115,17 @@ fn main() { .allowlist_function("rb_ary_push") .allowlist_function("rb_ary_unshift_m") .allowlist_function("rb_yjit_rb_ary_subseq_length") - - // From internal/array.h .allowlist_function("rb_ec_ary_new_from_values") .allowlist_function("rb_ary_tmp_new_from_values") - - // From include/ruby/internal/intern/class.h .allowlist_function("rb_class_attached_object") .allowlist_function("rb_singleton_class") - - // From include/ruby/internal/core/rclass.h .allowlist_function("rb_class_get_superclass") - - // From include/ruby/internal/gc.h .allowlist_function("rb_gc_mark") .allowlist_function("rb_gc_mark_movable") .allowlist_function("rb_gc_location") .allowlist_function("rb_gc_writebarrier") // VALUE variables for Ruby class objects - // From include/ruby/internal/globals.h .allowlist_var("rb_cBasicObject") .allowlist_var("rb_cModule") .allowlist_var("rb_cNilClass") @@ -178,84 +142,53 @@ fn main() { .allowlist_var("rb_cHash") .allowlist_var("rb_cClass") - // From include/ruby/internal/fl_type.h .allowlist_type("ruby_fl_type") .allowlist_type("ruby_fl_ushift") - - // From include/ruby/internal/core/robject.h .allowlist_type("ruby_robject_flags") - - // From include/ruby/internal/core/rarray.h .allowlist_type("ruby_rarray_flags") .allowlist_type("ruby_rarray_consts") - - // From include/ruby/internal/core/rclass.h .allowlist_type("ruby_rmodule_flags") - - // From ruby/internal/globals.h .allowlist_var("rb_mKernel") - - // From vm_callinfo.h .allowlist_type("vm_call_flag_bits") .allowlist_type("rb_call_data") .blocklist_type("rb_callcache.*") // Not used yet - opaque to make it easy to import rb_call_data .opaque_type("rb_callcache.*") .allowlist_type("rb_callinfo") - - // From vm_insnhelper.h .allowlist_var("VM_ENV_DATA_INDEX_ME_CREF") .allowlist_var("rb_block_param_proxy") - - // From include/ruby/internal/intern/range.h .allowlist_function("rb_range_new") - - // From include/ruby/internal/symbol.h .allowlist_function("rb_intern") .allowlist_function("rb_intern2") .allowlist_function("rb_id2sym") .allowlist_function("rb_id2name") .allowlist_function("rb_sym2id") .allowlist_function("rb_str_intern") - - // From internal/numeric.h .allowlist_function("rb_fix_aref") .allowlist_function("rb_float_plus") .allowlist_function("rb_float_minus") .allowlist_function("rb_float_mul") .allowlist_function("rb_float_div") - - // From internal/string.h .allowlist_type("ruby_rstring_private_flags") .allowlist_function("rb_ec_str_resurrect") .allowlist_function("rb_str_concat_literals") .allowlist_function("rb_obj_as_string_result") .allowlist_function("rb_str_byte_substr") .allowlist_function("rb_str_substr_two_fixnums") - - // From include/ruby/internal/intern/parse.h .allowlist_function("rb_backref_get") - - // From include/ruby/internal/intern/re.h .allowlist_function("rb_reg_last_match") .allowlist_function("rb_reg_match_pre") .allowlist_function("rb_reg_match_post") .allowlist_function("rb_reg_match_last") .allowlist_function("rb_reg_nth_match") - - // From internal/re.h .allowlist_function("rb_reg_new_ary") // `ruby_value_type` is a C enum and this stops it from // prefixing all the members with the name of the type .prepend_enum_name(false) .translate_enum_integer_types(true) // so we get fixed width Rust types for members - // From include/ruby/internal/value_type.h .allowlist_type("ruby_value_type") // really old C extension API - // From include/ruby/internal/hash.h .allowlist_type("ruby_rhash_flags") // really old C extension API - - // From method.h .allowlist_type("rb_method_visibility_t") .allowlist_type("rb_method_type_t") .allowlist_type("method_optimized_type") @@ -266,11 +199,7 @@ fn main() { .blocklist_type("rb_method_cfunc_t") .blocklist_type("rb_method_definition_.*") // Large struct with a bitfield and union of many types - don't import (yet?) .opaque_type("rb_method_definition_.*") - - // From numeric.c .allowlist_function("rb_float_new") - - // From vm_core.h .allowlist_var("rb_cRubyVM") .allowlist_var("rb_mRubyVMFrozenCore") .allowlist_var("VM_BLOCK_HANDLER_NONE") @@ -309,8 +238,6 @@ fn main() { .allowlist_type("vm_check_match_type") .allowlist_type("vm_opt_newarray_send_type") .allowlist_type("rb_iseq_type") - - // From yjit.c .allowlist_function("rb_object_shape_count") .allowlist_function("rb_ivar_get_at") .allowlist_function("rb_ivar_get_at_no_ractor_check") @@ -347,8 +274,6 @@ fn main() { .allowlist_function("rb_yjit_set_exception_return") .allowlist_function("rb_yjit_str_concat_codepoint") .allowlist_type("rstring_offsets") - - // From jit.c .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") @@ -356,58 +281,35 @@ fn main() { .allowlist_function("rb_jit_vm_unlock") .allowlist_function("rb_jit_for_each_iseq") .allowlist_type("robject_offsets") - - // from vm_sync.h .allowlist_function("rb_vm_barrier") // Not sure why it's picking these up, but don't. .blocklist_type("FILE") .blocklist_type("_IO_.*") - // From internal/compile.h .allowlist_function("rb_vm_insn_decode") - - // from internal/cont.h .allowlist_function("rb_jit_cont_each_iseq") - - // From iseq.h .allowlist_function("rb_vm_insn_addr2opcode") .allowlist_function("rb_iseqw_to_iseq") .allowlist_function("rb_iseq_label") .allowlist_function("rb_iseq_line_no") .allowlist_type("defined_type") - - // From builtin.h .allowlist_type("rb_builtin_function.*") - - // From internal/variable.h .allowlist_function("rb_gvar_(get|set)") .allowlist_function("rb_ensure_iv_list_size") - - // From include/ruby/internal/intern/variable.h .allowlist_function("rb_attr_get") .allowlist_function("rb_ivar_defined") .allowlist_function("rb_ivar_get") .allowlist_function("rb_mod_name") .allowlist_function("rb_const_get") - - // From internal/vm.h .allowlist_var("rb_vm_insn_count") - - // From include/ruby/internal/intern/vm.h .allowlist_function("rb_get_alloc_func") - - // From internal/object.h .allowlist_function("rb_class_allocate_instance") .allowlist_function("rb_obj_equal") .allowlist_function("rb_class_new_instance_pass_kw") .allowlist_function("rb_obj_alloc") - - // From gc.h and internal/gc.h .allowlist_function("rb_obj_info") .allowlist_function("ruby_xfree") - - // From include/ruby/debug.h .allowlist_function("rb_profile_frames") // Functions used for code generation @@ -491,7 +393,6 @@ fn main() { // We define VALUE manually, don't import it .blocklist_type("VALUE") - // From iseq.h .opaque_type("rb_iseq_t") .blocklist_type("rb_iseq_t") diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 56400e20cd742e..26bdfd2848373f 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -69,32 +69,21 @@ fn main() { // Import YARV bytecode instruction constants .allowlist_type("ruby_vminsn_type") - // From include/ruby/internal/special_consts.h .allowlist_type("ruby_special_consts") - - // From include/ruby/internal/intern/string.h .allowlist_function("rb_utf8_str_new") .allowlist_function("rb_str_buf_append") .allowlist_function("rb_str_dup") - - // From encindex.h .allowlist_type("ruby_preserved_encindex") - - // From include/ruby/ruby.h .allowlist_function("rb_class2name") // This struct is public to Ruby C extensions - // From include/ruby/internal/core/rbasic.h .allowlist_type("RBasic") - // From include/ruby/internal/core/rstring.h .allowlist_type("ruby_rstring_flags") - // From internal.h // This function prints info about a value and is useful for debugging .allowlist_function("rb_obj_info_dump") - // For testing .allowlist_function("ruby_init") .allowlist_function("ruby_init_stack") .allowlist_function("ruby_options") @@ -106,40 +95,25 @@ fn main() { // For crashing .allowlist_function("rb_bug") - // From shape.h .allowlist_function("rb_obj_shape_id") .allowlist_function("rb_shape_id_offset") .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") .allowlist_var("SHAPE_ID_NUM_BITS") - - // From ruby/internal/intern/object.h .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") .allowlist_function("rb_class_inherited_p") - - // From ruby/internal/encoding/encoding.h .allowlist_type("ruby_encoding_consts") - - // From include/hash.h .allowlist_function("rb_hash_new") - - // From internal/hash.h .allowlist_function("rb_hash_new_with_size") .allowlist_function("rb_hash_resurrect") .allowlist_function("rb_hash_stlike_foreach") .allowlist_function("rb_to_hash_type") - - // From include/ruby/st.h .allowlist_type("st_retval") - - // From include/ruby/internal/intern/hash.h .allowlist_function("rb_hash_aset") .allowlist_function("rb_hash_aref") .allowlist_function("rb_hash_bulk_insert") .allowlist_function("rb_hash_stlike_lookup") - - // From include/ruby/internal/intern/array.h .allowlist_function("rb_ary_new_capa") .allowlist_function("rb_ary_store") .allowlist_function("rb_ary_resurrect") @@ -149,20 +123,12 @@ fn main() { .allowlist_function("rb_ary_dup") .allowlist_function("rb_ary_push") .allowlist_function("rb_ary_unshift_m") - - // From internal/array.h .allowlist_function("rb_ec_ary_new_from_values") .allowlist_function("rb_ary_tmp_new_from_values") - - // From include/ruby/internal/intern/class.h .allowlist_function("rb_class_attached_object") .allowlist_function("rb_singleton_class") .allowlist_function("rb_define_class") - - // From include/ruby/internal/core/rclass.h .allowlist_function("rb_class_get_superclass") - - // From include/ruby/internal/gc.h .allowlist_function("rb_gc_mark") .allowlist_function("rb_gc_mark_movable") .allowlist_function("rb_gc_location") @@ -170,7 +136,6 @@ fn main() { .allowlist_function("rb_gc_writebarrier_remember") // VALUE variables for Ruby class objects - // From include/ruby/internal/globals.h .allowlist_var("rb_cBasicObject") .allowlist_var("rb_cObject") .allowlist_var("rb_cModule") @@ -192,38 +157,21 @@ fn main() { .allowlist_var("rb_cRegexp") .allowlist_var("rb_cISeq") - // From include/ruby/internal/fl_type.h .allowlist_type("ruby_fl_type") .allowlist_type("ruby_fl_ushift") - - // From include/ruby/internal/core/robject.h .allowlist_type("ruby_robject_flags") - - // From include/ruby/internal/core/rarray.h .allowlist_type("ruby_rarray_flags") .allowlist_type("ruby_rarray_consts") - - // From include/ruby/internal/core/rclass.h .allowlist_type("ruby_rmodule_flags") - - // From ruby/internal/globals.h .allowlist_var("rb_mKernel") - - // From vm_callinfo.h .allowlist_type("vm_call_flag_bits") .allowlist_type("rb_call_data") .blocklist_type("rb_callcache.*") // Not used yet - opaque to make it easy to import rb_call_data .opaque_type("rb_callcache.*") .allowlist_type("rb_callinfo") - - // From vm_insnhelper.h .allowlist_var("VM_ENV_DATA_INDEX_ME_CREF") .allowlist_var("rb_block_param_proxy") - - // From include/ruby/internal/intern/range.h .allowlist_function("rb_range_new") - - // From include/ruby/internal/symbol.h .allowlist_function("rb_intern") .allowlist_function("rb_intern2") .allowlist_function("rb_id2sym") @@ -231,38 +179,26 @@ fn main() { .allowlist_function("rb_str_intern") .allowlist_function("rb_id2str") .allowlist_function("rb_sym2str") - - // From internal/numeric.h .allowlist_function("rb_fix_aref") .allowlist_function("rb_float_plus") .allowlist_function("rb_float_minus") .allowlist_function("rb_float_mul") .allowlist_function("rb_float_div") - - // From internal/string.h .allowlist_type("ruby_rstring_private_flags") .allowlist_function("rb_ec_str_resurrect") .allowlist_function("rb_str_concat_literals") .allowlist_function("rb_obj_as_string_result") .allowlist_function("rb_str_byte_substr") .allowlist_function("rb_str_substr_two_fixnums") - - // From include/ruby/internal/intern/parse.h .allowlist_function("rb_backref_get") - - // From include/ruby/internal/intern/re.h .allowlist_function("rb_reg_last_match") .allowlist_function("rb_reg_match_pre") .allowlist_function("rb_reg_match_post") .allowlist_function("rb_reg_match_last") .allowlist_function("rb_reg_nth_match") - - // From internal/re.h .allowlist_function("rb_reg_new_ary") .allowlist_var("ARG_ENCODING_FIXED") .allowlist_var("ARG_ENCODING_NONE") - - // From include/ruby/onigmo.h .allowlist_var("ONIG_OPTION_IGNORECASE") .allowlist_var("ONIG_OPTION_EXTEND") .allowlist_var("ONIG_OPTION_MULTILINE") @@ -271,13 +207,9 @@ fn main() { // prefixing all the members with the name of the type .prepend_enum_name(false) .translate_enum_integer_types(true) // so we get fixed width Rust types for members - // From include/ruby/internal/value_type.h .allowlist_type("ruby_value_type") // really old C extension API - // From include/ruby/internal/hash.h .allowlist_type("ruby_rhash_flags") // really old C extension API - - // From method.h .allowlist_type("rb_method_visibility_t") .allowlist_type("rb_method_type_t") .allowlist_type("method_optimized_type") @@ -288,11 +220,7 @@ fn main() { .blocklist_type("rb_method_cfunc_t") .blocklist_type("rb_method_definition_.*") // Large struct with a bitfield and union of many types - don't import (yet?) .opaque_type("rb_method_definition_.*") - - // From numeric.c .allowlist_function("rb_float_new") - - // From vm_core.h .allowlist_var("rb_mRubyVMFrozenCore") .allowlist_var("VM_BLOCK_HANDLER_NONE") .allowlist_type("vm_frame_env_flags") @@ -331,8 +259,6 @@ fn main() { .allowlist_type("vm_opt_newarray_send_type") .allowlist_type("rb_iseq_type") .allowlist_type("rb_event_flag_t") - - // From zjit.c .allowlist_function("rb_object_shape_count") .allowlist_function("rb_iseq_(get|set)_zjit_payload") .allowlist_function("rb_iseq_pc_at_idx") @@ -362,8 +288,6 @@ fn main() { .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") .allowlist_var("RB_INVALID_SHAPE_ID") - - // From jit.c .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") @@ -372,59 +296,36 @@ fn main() { .allowlist_function("rb_jit_for_each_iseq") .allowlist_function("rb_iseq_reset_jit_func") .allowlist_type("robject_offsets") - - // from vm_sync.h .allowlist_function("rb_vm_barrier") // Not sure why it's picking these up, but don't. .blocklist_type("FILE") .blocklist_type("_IO_.*") - // From internal/compile.h .allowlist_function("rb_vm_insn_decode") - - // from internal/cont.h .allowlist_function("rb_jit_cont_each_iseq") - - // From iseq.h .allowlist_function("rb_vm_insn_addr2opcode") .allowlist_function("rb_iseqw_to_iseq") .allowlist_function("rb_iseq_label") .allowlist_function("rb_iseq_line_no") .allowlist_function("rb_iseq_defined_string") .allowlist_type("defined_type") - - // From builtin.h .allowlist_type("rb_builtin_function.*") - - // From internal/variable.h .allowlist_function("rb_gvar_(get|set)") .allowlist_function("rb_ensure_iv_list_size") - - // From include/ruby/internal/intern/variable.h .allowlist_function("rb_attr_get") .allowlist_function("rb_ivar_defined") .allowlist_function("rb_ivar_get") .allowlist_function("rb_ivar_set") .allowlist_function("rb_mod_name") - - // From internal/vm.h .allowlist_var("rb_vm_insn_count") - - // From include/ruby/internal/intern/vm.h .allowlist_function("rb_get_alloc_func") - - // From internal/object.h .allowlist_function("rb_class_allocate_instance") .allowlist_function("rb_obj_equal") .allowlist_function("rb_class_new_instance_pass_kw") .allowlist_function("rb_obj_alloc") - - // From gc.h and internal/gc.h .allowlist_function("rb_obj_info") .allowlist_function("ruby_xfree") - - // From include/ruby/debug.h .allowlist_function("rb_profile_frames") // Functions used for code generation @@ -505,7 +406,6 @@ fn main() { .blocklist_type("VALUE") .blocklist_type("ID") - // From iseq.h .opaque_type("rb_iseq_t") .blocklist_type("rb_iseq_t") From 30f85ce5302d4e8da5852726db9a2bc607b5aeec Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 11 Sep 2025 23:29:30 -0400 Subject: [PATCH 005/104] YJIT: Remove cargo from release builds * Release builds depend only on `rustc` for sake of packaging. Removing it from the image ensures that passing the CI implies that only `rustc` is required. --- .github/workflows/yjit-ubuntu.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 1961d1262ab3a0..3ff3310a44a5a0 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -145,6 +145,12 @@ jobs: if: ${{ matrix.rust_version }} run: rustup install ${{ matrix.rust_version }} --profile minimal + - name: Remove cargo + # Since this tests a `rustc` build for release, remove `cargo` to ensure + # that only `rustc` is used. + if: ${{ contains(matrix.configure, 'rustc') }} + run: sudo rm $(which -a cargo | uniq) + - name: Run configure run: ../src/configure -C --disable-install-doc --prefix=$(pwd)/install ${{ matrix.configure }} From f75e1cb362f57aa9e18c42026adec39b86d4e5c6 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 11 Sep 2025 23:32:06 -0400 Subject: [PATCH 006/104] ZJIT: Move jit.rs to ruby.rs and create a shared crate `jit` * ruby.rs should hold the main entrypoint to YJIT and ZJIT * The crate jit will hold code shared between them --- Cargo.lock | 16 +++++++++++----- Cargo.toml | 6 +++--- configure.ac | 4 ++-- defs/jit.mk | 2 +- jit/Cargo.toml | 6 ++++++ jit/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ jit.rs => ruby.rs | 0 yjit/Cargo.toml | 1 + zjit/Cargo.toml | 1 + 9 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 jit/Cargo.toml create mode 100644 jit/src/lib.rs rename jit.rs => ruby.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 6312cb46a9b2ca..9a4b2ebbbaf2ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,11 +62,7 @@ dependencies = [ [[package]] name = "jit" -version = "0.0.0" -dependencies = [ - "yjit", - "zjit", -] +version = "0.1.0" [[package]] name = "lazy_static" @@ -86,6 +82,14 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "ruby" +version = "0.0.0" +dependencies = [ + "yjit", + "zjit", +] + [[package]] name = "shlex" version = "1.3.0" @@ -176,6 +180,7 @@ name = "yjit" version = "0.1.0" dependencies = [ "capstone", + "jit", ] [[package]] @@ -184,4 +189,5 @@ version = "0.0.1" dependencies = [ "capstone", "insta", + "jit", ] diff --git a/Cargo.toml b/Cargo.toml index 3f373fdace9cbf..ec2ce880ca4c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,10 @@ # TODO(alan) notes about rust version requirements. Undecided yet. [workspace] -members = ["zjit", "yjit"] +members = ["zjit", "yjit", "jit"] [package] -name = "jit" +name = "ruby" version = "0.0.0" edition = "2024" rust-version = "1.85.0" @@ -18,7 +18,7 @@ zjit = { path = "zjit", optional = true } [lib] crate-type = ["staticlib"] -path = "jit.rs" +path = "ruby.rs" [features] disasm = ["yjit?/disasm", "zjit?/disasm"] diff --git a/configure.ac b/configure.ac index 47f8b79b038fbe..8cb30429dcddca 100644 --- a/configure.ac +++ b/configure.ac @@ -4019,9 +4019,9 @@ AS_IF([test x"$JIT_CARGO_SUPPORT" != "xno" -o \( x"$YJIT_SUPPORT" != "xno" -a x" ]) CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ - RUST_LIB="target/debug/libjit.a" + RUST_LIB="target/debug/libruby.a" ], [ - RUST_LIB="target/${JIT_CARGO_SUPPORT}/libjit.a" + RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" ]) ]) diff --git a/defs/jit.mk b/defs/jit.mk index 28d8f2da3a87f7..a537d803002856 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -17,7 +17,7 @@ CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) # ld: warning: object file (target/debug/libjit.a()) was built for # newer macOS version (15.2) than being linked (15.0) # This limits us to an older set of macOS API in the rust code, but we don't use any. -$(RUST_LIB): $(srcdir)/jit.rs +$(RUST_LIB): $(srcdir)/ruby.rs $(Q)if [ '$(ZJIT_SUPPORT)' != no -a '$(YJIT_SUPPORT)' != no ]; then \ echo 'building YJIT and ZJIT ($(JIT_CARGO_SUPPORT:yes=release) mode)'; \ elif [ '$(ZJIT_SUPPORT)' != no ]; then \ diff --git a/jit/Cargo.toml b/jit/Cargo.toml new file mode 100644 index 00000000000000..530fe3674b0345 --- /dev/null +++ b/jit/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "jit" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/jit/src/lib.rs b/jit/src/lib.rs new file mode 100644 index 00000000000000..6079d00f2fd886 --- /dev/null +++ b/jit/src/lib.rs @@ -0,0 +1,37 @@ +//! Shared code between YJIT and ZJIT. + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::alloc::{GlobalAlloc, Layout, System}; + +#[global_allocator] +pub static GLOBAL_ALLOCATOR: StatsAlloc = StatsAlloc { alloc_size: AtomicUsize::new(0) }; + +pub struct StatsAlloc { + pub alloc_size: AtomicUsize, +} + +unsafe impl GlobalAlloc for StatsAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.alloc_size.fetch_add(layout.size(), Ordering::SeqCst); + unsafe { System.alloc(layout) } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.alloc_size.fetch_sub(layout.size(), Ordering::SeqCst); + unsafe { System.dealloc(ptr, layout) } + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.alloc_size.fetch_add(layout.size(), Ordering::SeqCst); + unsafe { System.alloc_zeroed(layout) } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + if new_size > layout.size() { + self.alloc_size.fetch_add(new_size - layout.size(), Ordering::SeqCst); + } else if new_size < layout.size() { + self.alloc_size.fetch_sub(layout.size() - new_size, Ordering::SeqCst); + } + unsafe { System.realloc(ptr, layout, new_size) } + } +} diff --git a/jit.rs b/ruby.rs similarity index 100% rename from jit.rs rename to ruby.rs diff --git a/yjit/Cargo.toml b/yjit/Cargo.toml index ad7dd35ecfb173..af9c18c0dc1028 100644 --- a/yjit/Cargo.toml +++ b/yjit/Cargo.toml @@ -13,6 +13,7 @@ publish = false # Don't publish to crates.io # No required dependencies to simplify build process. TODO: Link to yet to be # written rationale. Optional For development and testing purposes capstone = { version = "0.13.0", optional = true } +jit = { version = "0.1.0", path = "../jit" } # NOTE: Development builds select a set of these via configure.ac # For debugging, `make V=1` shows exact cargo invocation. diff --git a/zjit/Cargo.toml b/zjit/Cargo.toml index 7334d465c28ba8..617cd11916db49 100644 --- a/zjit/Cargo.toml +++ b/zjit/Cargo.toml @@ -9,6 +9,7 @@ publish = false # Don't publish to crates.io # No required dependencies to simplify build process. TODO: Link to yet to be # written rationale. Optional For development and testing purposes capstone = { version = "0.13.0", optional = true } +jit = { version = "0.1.0", path = "../jit" } [dev-dependencies] insta = "1.43.1" From cd85fe5a87f3f7cac92b132f9f195a3d39685a37 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 11 Sep 2025 23:33:27 -0400 Subject: [PATCH 007/104] ZJIT: Add support for stats_allocator * Using the shared jit crate, support for a single global_allocator can function * Solves --zjit-mem-size --- common.mk | 2 ++ yjit/Cargo.toml | 1 + yjit/src/stats.rs | 43 ++++++------------------------------------ yjit/src/virtualmem.rs | 5 ++++- yjit/yjit.mk | 1 + zjit/Cargo.toml | 1 + zjit/src/options.rs | 10 +++++++--- zjit/src/state.rs | 2 +- zjit/src/stats.rs | 7 ++++++- zjit/src/virtualmem.rs | 11 ++++++++++- zjit/zjit.mk | 1 + 11 files changed, 40 insertions(+), 44 deletions(-) diff --git a/common.mk b/common.mk index 5cc7886796243f..ef7eb6ab58ca1e 100644 --- a/common.mk +++ b/common.mk @@ -266,6 +266,7 @@ MAKE_LINK = $(MINIRUBY) -rfileutils -e "include FileUtils::Verbose" \ YJIT_RUSTC_ARGS = --crate-name=yjit \ --crate-type=staticlib \ --edition=2021 \ + --cfg 'feature="stats_allocator"' \ -g \ -C lto=thin \ -C opt-level=3 \ @@ -276,6 +277,7 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \ ZJIT_RUSTC_ARGS = --crate-name=zjit \ --crate-type=staticlib \ --edition=2024 \ + --cfg 'feature="stats_allocator"' \ -g \ -C lto=thin \ -C opt-level=3 \ diff --git a/yjit/Cargo.toml b/yjit/Cargo.toml index af9c18c0dc1028..e2f1d84ffd3d8c 100644 --- a/yjit/Cargo.toml +++ b/yjit/Cargo.toml @@ -25,3 +25,4 @@ disasm = ["capstone"] # from cfg!(debug_assertions) so that we can see disasm of the code # that would run in the release mode. runtime_checks = [] +stats_allocator = [] diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index ea6130973d22e8..09971c5b3afb48 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -1,9 +1,8 @@ //! Everything related to the collection of runtime stats in YJIT //! See the --yjit-stats command-line option -use std::alloc::{GlobalAlloc, Layout, System}; use std::ptr::addr_of_mut; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::Ordering; use std::time::Instant; use std::collections::HashMap; @@ -12,6 +11,10 @@ use crate::cruby::*; use crate::options::*; use crate::yjit::{yjit_enabled_p, YJIT_INIT_TIME}; +#[cfg(feature = "stats_allocator")] +#[path = "../../jit/src/lib.rs"] +mod jit; + /// Running total of how many ISeqs are in the system. #[no_mangle] pub static mut rb_yjit_live_iseq_count: u64 = 0; @@ -20,43 +23,9 @@ pub static mut rb_yjit_live_iseq_count: u64 = 0; #[no_mangle] pub static mut rb_yjit_iseq_alloc_count: u64 = 0; -/// A middleware to count Rust-allocated bytes as yjit_alloc_size. -#[global_allocator] -static GLOBAL_ALLOCATOR: StatsAlloc = StatsAlloc { alloc_size: AtomicUsize::new(0) }; - -pub struct StatsAlloc { - alloc_size: AtomicUsize, -} - -unsafe impl GlobalAlloc for StatsAlloc { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - self.alloc_size.fetch_add(layout.size(), Ordering::SeqCst); - System.alloc(layout) - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - self.alloc_size.fetch_sub(layout.size(), Ordering::SeqCst); - System.dealloc(ptr, layout) - } - - unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - self.alloc_size.fetch_add(layout.size(), Ordering::SeqCst); - System.alloc_zeroed(layout) - } - - unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { - if new_size > layout.size() { - self.alloc_size.fetch_add(new_size - layout.size(), Ordering::SeqCst); - } else if new_size < layout.size() { - self.alloc_size.fetch_sub(layout.size() - new_size, Ordering::SeqCst); - } - System.realloc(ptr, layout, new_size) - } -} - /// The number of bytes YJIT has allocated on the Rust heap. pub fn yjit_alloc_size() -> usize { - GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst) + jit::GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst) } /// Mapping of C function / ISEQ name to integer indices diff --git a/yjit/src/virtualmem.rs b/yjit/src/virtualmem.rs index aa6d21f21035d4..97409c796cbca7 100644 --- a/yjit/src/virtualmem.rs +++ b/yjit/src/virtualmem.rs @@ -7,6 +7,9 @@ use std::{cell::RefCell, ptr::NonNull}; use crate::{backend::ir::Target, stats::yjit_alloc_size, utils::IntoUsize}; +#[cfg(test)] +use crate::options::get_option; + #[cfg(not(test))] pub type VirtualMem = VirtualMemory; @@ -411,7 +414,7 @@ pub mod tests { PAGE_SIZE.try_into().unwrap(), NonNull::new(mem_start as *mut u8).unwrap(), mem_size, - 128 * 1024 * 1024, + get_option!(mem_size), ) } diff --git a/yjit/yjit.mk b/yjit/yjit.mk index 6b22a15960c1f3..cf68edb29770b8 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -6,6 +6,7 @@ YJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/yjit/src/*/*.rs \ $(top_srcdir)/yjit/src/*/*/*.rs \ $(top_srcdir)/yjit/src/*/*/*/*.rs \ + $(top_srcdir)/jit/src/lib.rs \ ) # Because of Cargo cache, if the actual binary is not changed from the diff --git a/zjit/Cargo.toml b/zjit/Cargo.toml index 617cd11916db49..c97c845a6eff7f 100644 --- a/zjit/Cargo.toml +++ b/zjit/Cargo.toml @@ -20,3 +20,4 @@ insta = "1.43.1" # Support --yjit-dump-disasm and RubyVM::YJIT.disasm using libcapstone. disasm = ["capstone"] runtime_checks = [] +stats_allocator = [] diff --git a/zjit/src/options.rs b/zjit/src/options.rs index dbb6ee8ebbdf24..2a9b2e7d279055 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -35,6 +35,10 @@ pub struct Options { /// Note that the command line argument is expressed in MiB and not bytes. pub exec_mem_bytes: usize, + /// Hard limit of ZJIT's total memory usage. + /// Note that the command line argument is expressed in MiB and not bytes. + pub mem_bytes: usize, + /// Number of times YARV instructions should be profiled. pub num_profiles: u8, @@ -79,6 +83,7 @@ impl Default for Options { fn default() -> Self { Options { exec_mem_bytes: 64 * 1024 * 1024, + mem_bytes: 128 * 1024 * 1024, num_profiles: DEFAULT_NUM_PROFILES, stats: false, print_stats: false, @@ -100,9 +105,8 @@ impl Default for Options { /// Note that --help allows only 80 chars per line, including indentation, and it also puts the /// description in a separate line if the option name is too long. 80-char limit --> | (any character beyond this `|` column fails the test) pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ - // TODO: Hide --zjit-exec-mem-size from ZJIT_OPTIONS once we add --zjit-mem-size (Shopify/ruby#686) - ("--zjit-exec-mem-size=num", - "Size of executable memory block in MiB (default: 64)."), + ("--zjit-mem-size=num", + "Max amount of memory that ZJIT can use (in MiB)."), ("--zjit-call-threshold=num", "Number of calls to trigger JIT (default: 2)."), ("--zjit-num-profiles=num", diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 02bba3b7a3e8a8..da97829e43629b 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -85,7 +85,7 @@ impl ZJITState { page_size, NonNull::new(virt_block).unwrap(), exec_mem_bytes, - exec_mem_bytes, // TODO: change this to --zjit-mem-size (Shopify/ruby#686) + get_option!(mem_bytes) ); let mem_block = Rc::new(RefCell::new(mem_block)); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index d1a6d584b9c020..98ddc20226621b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -1,6 +1,11 @@ //! Counters and associated methods for events when ZJIT is run. use std::time::Instant; +use std::sync::atomic::Ordering; + +#[cfg(feature = "stats_allocator")] +#[path = "../../jit/src/lib.rs"] +mod jit; use crate::{cruby::*, hir::ParseError, options::get_option, state::{zjit_enabled_p, ZJITState}}; @@ -353,5 +358,5 @@ pub fn with_time_stat(counter: Counter, func: F) -> R where F: FnOnce() -> /// The number of bytes ZJIT has allocated on the Rust heap. pub fn zjit_alloc_size() -> usize { - 0 // TODO: report the actual memory usage to support --zjit-mem-size (Shopify/ruby#686) + jit::GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst) } diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 42ce525fde7a6c..11de4e08afe962 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -7,6 +7,9 @@ use std::ptr::NonNull; use crate::stats::zjit_alloc_size; +#[cfg(test)] +use crate::options::get_option; + #[cfg(not(test))] pub type VirtualMem = VirtualMemory; @@ -369,6 +372,12 @@ pub mod tests { // Fictional architecture where each page is 4 bytes long const PAGE_SIZE: usize = 4; fn new_dummy_virt_mem() -> VirtualMemory { + unsafe { + if crate::options::OPTIONS.is_none() { + crate::options::OPTIONS = Some(crate::options::Options::default()); + } + } + let mem_size = PAGE_SIZE * 10; let alloc = TestingAllocator::new(mem_size); let mem_start: *const u8 = alloc.mem_start(); @@ -378,7 +387,7 @@ pub mod tests { PAGE_SIZE.try_into().unwrap(), NonNull::new(mem_start as *mut u8).unwrap(), mem_size, - 128 * 1024 * 1024, + get_option!(mem_bytes), ) } diff --git a/zjit/zjit.mk b/zjit/zjit.mk index be989bdecd41c3..f0bf1b0da59fb2 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -9,6 +9,7 @@ ZJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/zjit/src/*/*.rs \ $(top_srcdir)/zjit/src/*/*/*.rs \ $(top_srcdir)/zjit/src/*/*/*/*.rs \ + $(top_srcdir)/jit/src/lib.rs \ ) $(RUST_LIB): $(ZJIT_SRC_FILES) From 6d2c7d7d0ff7029c39f5137a228aa15e45f01d7b Mon Sep 17 00:00:00 2001 From: Matheus Richard Date: Wed, 10 Sep 2025 14:12:39 -0300 Subject: [PATCH 008/104] [rubygems/rubygems] Raise error on missing version file If the file option is given but the file not found, raise a GemfileError with a message indicating the file was not found. Currently this is raising a generic Errno::ENOENT error. https://github.com/rubygems/rubygems/commit/db61de6b21 --- lib/bundler/ruby_dsl.rb | 2 ++ spec/bundler/bundler/ruby_dsl_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb index cd88253f463374..db4d5521e54925 100644 --- a/lib/bundler/ruby_dsl.rb +++ b/lib/bundler/ruby_dsl.rb @@ -57,6 +57,8 @@ def normalize_ruby_file(filename) else file_content.strip end + rescue Errno::ENOENT + raise GemfileError, "Could not find version file #{filename}" end end end diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb index 2607f746e76653..0d02542fb595b3 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -210,6 +210,16 @@ class MockDSL it_behaves_like "it stores the ruby version" end end + + context "when the file does not exist" do + let(:ruby_version_file_path) { nil } + let(:ruby_version_arg) { nil } + let(:file) { "nonexistent.txt" } + + it "raises an error" do + expect { subject }.to raise_error(Bundler::GemfileError, /Could not find version file nonexistent.txt/) + end + end end end end From e6ca24f1166d7f4840ca8c78a2933f86d7c03a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:00:00 +0200 Subject: [PATCH 009/104] [rubygems/rubygems] If name is to be unlocked, we can skip converging the spec https://github.com/rubygems/rubygems/commit/744b35412e --- lib/bundler/definition.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index e400c38cec54dc..bdde7f253aaeea 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1036,6 +1036,8 @@ def converge_specs(specs) specs.each do |s| name = s.name + next if @gems_to_unlock.include?(name) + dep = @dependencies.find {|d| s.satisfies?(d) } lockfile_source = s.source @@ -1054,7 +1056,7 @@ def converge_specs(specs) next if @sources_to_unlock.include?(source.name) # Path sources have special logic - if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || (source.instance_of?(Source::Git) && !@gems_to_unlock.include?(name) && deps.include?(dep)) + if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || (source.instance_of?(Source::Git) && deps.include?(dep)) new_spec = source.specs[s].first if new_spec s.runtime_dependencies.replace(new_spec.runtime_dependencies) From 2330033a90f580be47a58748c178570ab5c3de3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:33:49 +0200 Subject: [PATCH 010/104] [rubygems/rubygems] Remove unnecessary condition It sounds like this should apply to all git sources at this point. https://github.com/rubygems/rubygems/commit/b1817f91de --- lib/bundler/definition.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index bdde7f253aaeea..3f406079eb990b 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1056,7 +1056,7 @@ def converge_specs(specs) next if @sources_to_unlock.include?(source.name) # Path sources have special logic - if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || (source.instance_of?(Source::Git) && deps.include?(dep)) + if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || source.instance_of?(Source::Git) new_spec = source.specs[s].first if new_spec s.runtime_dependencies.replace(new_spec.runtime_dependencies) From 92bddb4529b7c788cc627b11a90917d31b33072b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:42:47 +0200 Subject: [PATCH 011/104] [rubygems/rubygems] Consolidate condition on path sources It matches the comment above more naturally and it's consistent with how the same thing is checked in other places. https://github.com/rubygems/rubygems/commit/59ec6b4b29 --- lib/bundler/definition.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 3f406079eb990b..8b1e082ae9928d 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1056,7 +1056,7 @@ def converge_specs(specs) next if @sources_to_unlock.include?(source.name) # Path sources have special logic - if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || source.instance_of?(Source::Git) + if source.is_a?(Source::Path) new_spec = source.specs[s].first if new_spec s.runtime_dependencies.replace(new_spec.runtime_dependencies) From 98beabd66687b741cd2d72d7e0517ae23100d2ff Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:18:36 +0200 Subject: [PATCH 012/104] [rubygems/rubygems] Fix outdated lockfile during `bundle lock` when source changes When the source used to be git and switches back to rubygems, it is possible that the git source contains a version that ruybgems doesn't know about yet. So don't add the locked spec to the base resolve, and also don't add a lower bound requirement on the version, since the version in the new source may actually be lower. https://github.com/rubygems/rubygems/commit/85514e3a1e --- lib/bundler/definition.rb | 3 ++- spec/bundler/lock/git_spec.rb | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 8b1e082ae9928d..e177b6e39673c7 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1051,6 +1051,7 @@ def converge_specs(specs) # Replace the locked dependency's source with the equivalent source from the Gemfile s.source = replacement_source || default_source + next if s.source_changed? source = s.source next if @sources_to_unlock.include?(source.name) @@ -1138,7 +1139,7 @@ def lockfiles_equal?(current, proposed, preserve_unknown_sections) def additional_base_requirements_to_prevent_downgrades(resolution_base) return resolution_base unless @locked_gems && !sources.expired_sources?(@locked_gems.sources) @originally_locked_specs.each do |locked_spec| - next if locked_spec.source.is_a?(Source::Path) + next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed? name = locked_spec.name next if @changed_dependencies.include?(name) diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb index 49c0a2af1c20fc..4e416518305aa8 100644 --- a/spec/bundler/lock/git_spec.rb +++ b/spec/bundler/lock/git_spec.rb @@ -220,4 +220,39 @@ expect(lockfile).to include("securerandom (0.3.2)") end + + it "does not lock versions that don't exist in the repository when changing a GIT direct dep to a GEM direct dep" do + build_repo4 do + build_gem "ruby-lsp", "0.16.1" + end + + path = lib_path("ruby-lsp") + revision = build_git("ruby-lsp", "0.16.2", path: path).ref_for("HEAD") + + lockfile <<~L + GIT + remote: #{path} + revision: #{revision} + specs: + ruby-lsp (0.16.2) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp! + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + gem "ruby-lsp" + G + + bundle "lock" + + expect(lockfile).to include("ruby-lsp (0.16.1)") + end end From 09ae509c1f5f6804d8f43564d9e60a7d711074a7 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Sep 2025 10:04:40 +0200 Subject: [PATCH 013/104] [ruby/json] Enable coverage before loading simplecov Fix: https://github.com/ruby/json/pull/853 Simplecov end up requiring json so we need to start collecting coverage before. https://github.com/ruby/json/commit/ca72019fd3 --- test/json/test_helper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/json/test_helper.rb b/test/json/test_helper.rb index cf592debfd8c43..a788804ddeca4e 100644 --- a/test/json/test_helper.rb +++ b/test/json/test_helper.rb @@ -1,5 +1,8 @@ $LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__)) +require 'coverage' +Coverage.start + begin require 'simplecov' rescue LoadError From 7a05dbc47831a655a1ef8a1635f88292acd325da Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 8 Sep 2025 13:30:44 +0200 Subject: [PATCH 014/104] File.dirname: return consistent encoding for `"."` [Bug #21561] It's preferable if the method is consistent in the encoding in the returned string. --- file.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/file.c b/file.c index ff755b4ac312d7..4fc2fec75f59a6 100644 --- a/file.c +++ b/file.c @@ -5060,8 +5060,11 @@ rb_file_dirname_n(VALUE fname, int n) break; } } - if (p == name) - return rb_usascii_str_new2("."); + if (p == name) { + dirname = rb_str_new(".", 1); + rb_enc_copy(dirname, fname); + return dirname; + } #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(name) && isdirsep(*(name + 2))) { const char *top = skiproot(name + 2, end, enc); From d39fdee9e5653cf00971b4e77d8e14e10d012bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 21 Aug 2025 16:52:38 +0200 Subject: [PATCH 015/104] Remove redefined method warnings from TestRubyOptimization#test_opt_new --- test/ruby/test_optimization.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index e39eafa5e50bb9..90ef0a102f8b80 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -1256,6 +1256,9 @@ def initialize a, **kw insn = iseq.disasm assert_match(/opt_new/, insn) assert_match(/OptNewFoo:.+@a=1, @b=2/, iseq.eval.inspect) + # clean up to avoid warnings + Object.send :remove_const, :OptNewFoo + Object.remove_method :optnew_foo if defined?(optnew_foo) end [ 'def optnew_foo(&) = OptNewFoo.new(&)', From 1902c42b1f3d81874e1d8503a1f2d373c1d5590a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 21 Aug 2025 16:53:44 +0200 Subject: [PATCH 016/104] Remove block may be ignored warnings from TestRubyOptimization#test_block_given_aset_aref --- test/ruby/test_optimization.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 90ef0a102f8b80..089c5fbd1d0dc6 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -606,11 +606,11 @@ def foo(n) end class Bug10557 - def [](_) + def [](_, &) block_given? end - def []=(_, _) + def []=(_, _, &) block_given? end end From af210467b5d513fa7444cd64023f207aa2aaea14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 22 Aug 2025 10:38:37 +0200 Subject: [PATCH 017/104] Suppress warnings when testing RubyVM::AbstractSyntaxTree --- test/ruby/test_ast.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 55e5915d821546..c7a946dec868bb 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -48,7 +48,7 @@ def initialize(path, src: nil) @path = path @errors = [] @debug = false - @ast = RubyVM::AbstractSyntaxTree.parse(src) if src + @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse(src) } if src end def validate_range @@ -67,7 +67,7 @@ def validate_not_cared def ast return @ast if defined?(@ast) - @ast = RubyVM::AbstractSyntaxTree.parse_file(@path) + @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file(@path) } end private @@ -135,7 +135,7 @@ def validate_not_cared0(node) Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| define_method("test_all_tokens:#{path}") do - node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) + node = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) } tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] } tokens_bytes = tokens.map { _1[2]}.join.bytes source_bytes = File.read("#{SRCDIR}/#{path}").bytes From 38ec296ce5e7e4cf0ac49b2fa43eec9e6a53e269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 22 Aug 2025 11:38:24 +0200 Subject: [PATCH 018/104] Remove unused variable warning --- test/ruby/test_variable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index df0c6f1f094578..68434e0b6c479b 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -512,7 +512,7 @@ def test_genivar_cache instance.instance_variable_set(:@a3, 3) instance.instance_variable_set(:@a4, 4) end.resume - assert_equal 4, instance.instance_variable_get(:@a4) + assert_equal 4, instance.instance_variable_get(:@a4), bug21547 end private From 2687ecaf6fc3e07ce3cbe089d0537eb94518c082 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 11 Sep 2025 10:51:00 -0400 Subject: [PATCH 019/104] Fix use of uninitialized memory in strings Strings created from the C API with a len but no ptr have a buffer allocated and the length set, but the buffer is not zero'd. This causes use of uninitialized memory and allows reading memory that previously existed there. For example, the rb_str_tmp_new spec fails when we create a string with a large length greater than 24 bytes (since we zero the first 24 bytes of the slot). --- spec/ruby/optional/capi/string_spec.rb | 18 +++++++++++++----- string.c | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index be9cb9015f0d34..605c43769ddb0b 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -191,11 +191,19 @@ def inspect end it "returns a new String object filled with \\0 bytes" do - s = @s.rb_str_tmp_new(4) - s.encoding.should == Encoding::BINARY - s.bytesize.should == 4 - s.size.should == 4 - s.should == "\x00\x00\x00\x00" + lens = [4] + + ruby_version_is "3.5" do + lens << 100 + end + + lens.each do |len| + s = @s.rb_str_tmp_new(len) + s.encoding.should == Encoding::BINARY + s.bytesize.should == len + s.size.should == len + s.should == "\x00" * len + end end end diff --git a/string.c b/string.c index 20873a35a5579a..7b8a55a5358e7a 100644 --- a/string.c +++ b/string.c @@ -1066,6 +1066,9 @@ str_enc_new(VALUE klass, const char *ptr, long len, rb_encoding *enc) if (ptr) { memcpy(RSTRING_PTR(str), ptr, len); } + else { + memset(RSTRING_PTR(str), 0, len); + } STR_SET_LEN(str, len); TERM_FILL(RSTRING_PTR(str) + len, termlen); From aca0faf5fd34b1494c417bec75fe8f3baa05db0e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 4 Jul 2024 23:35:27 +0900 Subject: [PATCH 020/104] [ruby/openssl] pkey: add more tests for OpenSSL::PKey.read Add tests covering edge cases in the current behavior to prevent accidental regressions. The next patches will update the OpenSSL 3.x path. https://github.com/ruby/openssl/commit/468f8ceea2 --- test/openssl/test_pkey.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 71f5da81d145e7..9fe4afc9f2709f 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -60,6 +60,31 @@ def test_s_generate_key assert_not_equal nil, pkey.private_key end + def test_s_read_pem_unknown_block + # A PEM-encoded certificate and a PEM-encoded private key are combined. + # Check that OSSL_STORE doesn't stop after the first PEM block. + orig = Fixtures.pkey("rsa-1") + subject = OpenSSL::X509::Name.new([["CN", "test"]]) + cert = issue_cert(subject, orig, 1, [], nil, nil) + + input = cert.to_text + cert.to_pem + orig.to_text + orig.private_to_pem + pkey = OpenSSL::PKey.read(input) + assert_equal(orig.private_to_der, pkey.private_to_der) + end + + def test_s_read_der_then_pem + # If the input is valid as both DER and PEM (which allows garbage data + # before and after the block), it is read as DER + # + # TODO: Garbage data after DER should not be allowed, but it is currently + # ignored + orig1 = Fixtures.pkey("rsa-1") + orig2 = Fixtures.pkey("rsa-2") + pkey = OpenSSL::PKey.read(orig1.public_to_der + orig2.private_to_pem) + assert_equal(orig1.public_to_der, pkey.public_to_der) + assert_not_predicate(pkey, :private?) + end + def test_hmac_sign_verify pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" }) From 8af8582d4c3baf0ba41f8b54b43839ec8ba3dc3d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 3 Aug 2025 19:50:04 +0900 Subject: [PATCH 021/104] [ruby/openssl] pkey: pass pem_password_cb to OSSL_DECODER only when it is needed Specify OSSL_DECODER_CTX_set_pem_password_cb() only when we expect a passphrase-protected private key. OSSL_DECODER appears to try to decrypt every PEM block in the input even when the PEM header does not match the requested selection. This can cause repeated prompts for a passphrase in a single OpenSSL::PKey.read call. https://github.com/ruby/openssl/commit/933503f49f --- ext/openssl/ossl_pkey.c | 3 +- test/openssl/test_pkey.rb | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index e88074ddf2582c..0fed03332fc31e 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -94,7 +94,8 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass) selection, NULL, NULL); if (!dctx) goto out; - if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, + if (selection == EVP_PKEY_KEYPAIR && + OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, ppass) != 1) goto out; while (1) { diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 9fe4afc9f2709f..8066c4dc190fa9 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -85,6 +85,86 @@ def test_s_read_der_then_pem assert_not_predicate(pkey, :private?) end + def test_s_read_passphrase + orig = Fixtures.pkey("rsa-1") + encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase") + assert_match(/\A-----BEGIN ENCRYPTED PRIVATE KEY-----/, encrypted_pem) + + # Correct passphrase passed as the second argument + pkey1 = OpenSSL::PKey.read(encrypted_pem, "correct_passphrase") + assert_equal(orig.private_to_der, pkey1.private_to_der) + + # Correct passphrase returned by the block. The block gets false + called = 0 + flag = nil + pkey2 = OpenSSL::PKey.read(encrypted_pem) { |f| + called += 1 + flag = f + "correct_passphrase" + } + assert_equal(orig.private_to_der, pkey2.private_to_der) + assert_equal(1, called) + assert_false(flag) + + # Incorrect passphrase passed. The block is not called + called = 0 + assert_raise(OpenSSL::PKey::PKeyError) { + OpenSSL::PKey.read(encrypted_pem, "incorrect_passphrase") { + called += 1 + } + } + assert_equal(0, called) + + # Incorrect passphrase returned by the block. The block is called only once + called = 0 + assert_raise(OpenSSL::PKey::PKeyError) { + OpenSSL::PKey.read(encrypted_pem) { + called += 1 + "incorrect_passphrase" + } + } + assert_equal(1, called) + end + + def test_s_read_passphrase_tty + omit "https://github.com/aws/aws-lc/pull/2555" if aws_lc? + + orig = Fixtures.pkey("rsa-1") + encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase") + + # Correct passphrase passed to OpenSSL's prompt + script = <<~"end;" + require "openssl" + Process.setsid + OpenSSL::PKey.read(#{encrypted_pem.dump}) + puts "ok" + end; + assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"], + "correct_passphrase\n") { |stdout, stderr| + assert_equal(["Enter PEM pass phrase:"], stderr) + assert_equal(["ok"], stdout) + } + + # Incorrect passphrase passed to OpenSSL's prompt + script = <<~"end;" + require "openssl" + Process.setsid + begin + OpenSSL::PKey.read(#{encrypted_pem.dump}) + rescue OpenSSL::PKey::PKeyError + puts "ok" + else + puts "expected OpenSSL::PKey::PKeyError" + end + end; + stdin = "incorrect_passphrase\n" * 5 + assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"], + stdin) { |stdout, stderr| + assert_equal(1, stderr.count("Enter PEM pass phrase:")) + assert_equal(["ok"], stdout) + } + end if ENV["OSSL_TEST_ALL"] == "1" && Process.respond_to?(:setsid) + def test_hmac_sign_verify pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" }) From c0820058243842d1391d896baf67914a8ea50e13 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 13 Aug 2025 03:08:22 +0900 Subject: [PATCH 022/104] [ruby/openssl] pkey: stop retrying after non-retryable error from OSSL_DECODER Continue processing only when OSSL_DECODER_from_bio() returns the error code ERR_R_UNSUPPORTED. Otherwise, raise an exception without retrying decoding the input in another format. This fixes another case where OpenSSL::PKey.read prompts for a passphrase multiple times when the input contains multiple passphrase-protected PEM blocks and the first one cannot be decoded. I am not entirely sure if the error code ERR_R_UNSUPPORTED is considered part of the public interface of OpenSSL, but this seems to be the only option available and is the approach used internally by the PEM_read_bio_*() functions. Fixes https://github.com/ruby/openssl/issues/927 https://github.com/ruby/openssl/commit/985ba27d63 --- ext/openssl/ossl_pkey.c | 38 ++++++++++++++++++++++++-------------- test/openssl/test_pkey.rb | 11 +++++++++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 0fed03332fc31e..481bd8a8ee09e3 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -83,13 +83,15 @@ ossl_pkey_wrap(EVP_PKEY *pkey) # include static EVP_PKEY * -ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass) +ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, + int *retryable) { 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) @@ -100,17 +102,22 @@ 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; + if (ERR_GET_REASON(ERR_peek_error()) != ERR_R_UNSUPPORTED) + break; + if (BIO_eof(bio) == 1) { + *retryable = 1; + break; + } pos2 = BIO_tell(bio); - if (pos2 < 0 || pos2 <= pos) + if (pos2 < 0 || pos2 <= pos) { + *retryable = 1; break; + } ossl_clear_error(); pos = pos2; } out: - OSSL_BIO_reset(bio); OSSL_DECODER_CTX_free(dctx); return pkey; } @@ -118,7 +125,6 @@ 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 *)); @@ -167,18 +173,22 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass) EVP_PKEY_PUBLIC_KEY }; int selection_num = (int)(sizeof(selections) / sizeof(int)); - int i, j; - 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; + 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); } + + int retryable; + EVP_PKEY *pkey = ossl_pkey_read(bio, input_types[i], selections[j], + pass, &retryable); + if (pkey || !retryable) + return pkey; } } - out: - return pkey; + return NULL; } #else EVP_PKEY * diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 8066c4dc190fa9..24a595333b4ea3 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -124,6 +124,17 @@ 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 a8c5b2bf510161b56a18ee7e9fd96ca63b04f393 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 08:12:09 -0500 Subject: [PATCH 023/104] [DOC] Tweaks for String#lstrip --- string.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index 7b8a55a5358e7a..dd275ba0754d46 100644 --- a/string.c +++ b/string.c @@ -10404,10 +10404,11 @@ rb_str_lstrip_bang(VALUE str) * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " - * s.lstrip # => "abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * s.lstrip + * # => "abc\u0000\t\n\v\f\r " * - * Related: String#rstrip, String#strip. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From fbeeb89b3d442d3be27544b058cc9d5b5dc4accc Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 08:14:24 -0500 Subject: [PATCH 024/104] [DOC] Tweaks for String#match? (#14477) --- string.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/string.c b/string.c index dd275ba0754d46..ca5d83725285e9 100644 --- a/string.c +++ b/string.c @@ -5119,24 +5119,23 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str) * call-seq: * match?(pattern, offset = 0) -> true or false * - * Returns +true+ or +false+ based on whether a match is found for +self+ and +pattern+. + * Returns whether a match is found for +self+ and the given arguments; + * does not update {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * - * Note: does not update Regexp@Global+Variables. + * Computes +regexp+ by converting +pattern+ (if not already a Regexp): * - * Computes +regexp+ by converting +pattern+ (if not already a Regexp). * regexp = Regexp.new(pattern) * - * Returns +true+ if self+.match(regexp) returns a MatchData object, + * Returns +true+ if self[offset..].match(regexp) returns a MatchData object, * +false+ otherwise: * * 'foo'.match?(/o/) # => true * 'foo'.match?('o') # => true * 'foo'.match?(/x/) # => false - * - * If Integer argument +offset+ is given, the search begins at index +offset+: * 'foo'.match?('f', 1) # => false * 'foo'.match?('o', 1) # => true * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE From 95ae42cea1df8015876d185387091a4fb617b82d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 08:30:50 -0500 Subject: [PATCH 025/104] [DOC] Tweaks for String#match (#14476) --- string.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/string.c b/string.c index ca5d83725285e9..8d27248020cded 100644 --- a/string.c +++ b/string.c @@ -5070,34 +5070,36 @@ static VALUE get_pat(VALUE); * match(pattern, offset = 0) -> matchdata or nil * match(pattern, offset = 0) {|matchdata| ... } -> object * - * Returns a MatchData object (or +nil+) based on +self+ and the given +pattern+. - * - * Note: also updates Regexp@Global+Variables. + * Creates a MatchData object based on +self+ and the given arguments; + * updates {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * * - Computes +regexp+ by converting +pattern+ (if not already a Regexp). + * * regexp = Regexp.new(pattern) + * * - Computes +matchdata+, which will be either a MatchData object or +nil+ * (see Regexp#match): - * matchdata = regexp.match(self) - * - * With no block given, returns the computed +matchdata+: * - * 'foo'.match('f') # => # - * 'foo'.match('o') # => # - * 'foo'.match('x') # => nil + * matchdata = regexp.match(self[offset..]) * - * If Integer argument +offset+ is given, the search begins at index +offset+: + * With no block given, returns the computed +matchdata+ or +nil+: * + * 'foo'.match('f') # => # + * 'foo'.match('o') # => # + * 'foo'.match('x') # => nil * 'foo'.match('f', 1) # => nil * 'foo'.match('o', 1) # => # * - * With a block given, calls the block with the computed +matchdata+ - * and returns the block's return value: + * With a block given and computed +matchdata+ non-nil, calls the block with +matchdata+; + * returns the block's return value: * * 'foo'.match(/o/) {|matchdata| matchdata } # => # - * 'foo'.match(/x/) {|matchdata| matchdata } # => nil - * 'foo'.match(/f/, 1) {|matchdata| matchdata } # => nil * + * With a block given and +nil+ +matchdata+, does not call the block: + * + * 'foo'.match(/x/) {|matchdata| fail 'Cannot happen' } # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE From adcde78dbf0cc6f6a536aa15c0807f15c9daa22f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 11 Sep 2025 08:47:08 -0400 Subject: [PATCH 026/104] Use IMEMO_NEW in rb_imemo_tmpbuf_new --- imemo.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/imemo.c b/imemo.c index abf101f362f325..02cba387bd22be 100644 --- a/imemo.c +++ b/imemo.c @@ -51,11 +51,7 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) static rb_imemo_tmpbuf_t * rb_imemo_tmpbuf_new(void) { - size_t size = sizeof(struct rb_imemo_tmpbuf_struct); - VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); - NEWOBJ_OF(obj, struct rb_imemo_tmpbuf_struct, 0, flags, size, 0); - - return obj; + return IMEMO_NEW(rb_imemo_tmpbuf_t, imemo_tmpbuf, 0); } void * From 5a2cedd051634d6d1c8fbf48b0327f8cd8eec495 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:14:59 +0900 Subject: [PATCH 027/104] Just touch the timestamp for prism/srcs.mk when no baseruby --- prism/srcs.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prism/srcs.mk b/prism/srcs.mk index aa5c0fa2b5ee33..565d793cc0a210 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -5,6 +5,9 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs uncommon.mk: prism/.srcs.mk.time prism/.srcs.mk.time: +prism/$(HAVE_BASERUBY:no=.srcs.mk.time): + mkdir -p $(@D) + touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ $(PRISM_SRCDIR)/srcs.mk.in From 8a30594d2cf22c223d57f0fb3832c67b5bbdc76d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 12 Sep 2025 23:02:27 +0900 Subject: [PATCH 028/104] [ruby/openssl] pkey: fix loading public keys with early OpenSSL 3.0.x releases Treat an empty error queue after calling OSSL_DECODER_from_bio() as a retryable error. This is a follow-up to the previous commit https://github.com/ruby/openssl/commit/985ba27d6339 (pkey: stop retrying after non-retryable error from OSSL_DECODER). The commit broke loading public keys on Ubuntu 22.04 LTS, which ships OpenSSL 3.0.2. https://github.com/ruby/openssl/commit/5347880c6e --- ext/openssl/ossl_pkey.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 481bd8a8ee09e3..ee6a7a988f59f0 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -103,7 +103,10 @@ ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass, while (1) { if (OSSL_DECODER_from_bio(dctx, bio) == 1) break; - if (ERR_GET_REASON(ERR_peek_error()) != ERR_R_UNSUPPORTED) + // 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; From 691a5545e6161cf8c2f574ae9fc69df1232cda97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 12 Sep 2025 16:16:52 +0200 Subject: [PATCH 029/104] Restore test example for argument forwarding Since cb419e3912f0514b8151469b0a4a4b83cbbcce78 we're no longer testing this case because foo is redefined on obj1. --- test/ruby/test_syntax.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index b7e021a4ff4976..bbdeb876ec3eb0 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -2027,10 +2027,11 @@ def obj1.bar(*args, **kws, &block) end obj4 = obj1.clone obj5 = obj1.clone + obj6 = obj1.clone obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) - obj1.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__) obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__) + obj6.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) klass = Class.new { def foo(*args, **kws, &block) @@ -2059,7 +2060,7 @@ def obj3.bar(*args, &block) end obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) - [obj1, obj2, obj3, obj4, obj5].each do |obj| + [obj1, obj2, obj3, obj4, obj5, obj6].each do |obj| assert_warning('') { assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x}) } From a35ceeedfada7cd556462401bef795470c38d41f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Sep 2025 09:19:18 -0700 Subject: [PATCH 030/104] Revert "Just touch the timestamp for prism/srcs.mk when no baseruby" This reverts commit 5a2cedd051634d6d1c8fbf48b0327f8cd8eec495. The CI is telling us to revert the diff: https://github.com/ruby/ruby/actions/runs/17679836157/job/50251138515 --- prism/srcs.mk | 3 --- 1 file changed, 3 deletions(-) diff --git a/prism/srcs.mk b/prism/srcs.mk index 565d793cc0a210..aa5c0fa2b5ee33 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -5,9 +5,6 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs uncommon.mk: prism/.srcs.mk.time prism/.srcs.mk.time: -prism/$(HAVE_BASERUBY:no=.srcs.mk.time): - mkdir -p $(@D) - touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ $(PRISM_SRCDIR)/srcs.mk.in From d8e9ec6693d5f88457b35a3cbf890f8247cfe20e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Sep 2025 18:01:45 +0100 Subject: [PATCH 031/104] ZJIT: Add specific dynamic send type counters (#14528) --- zjit.rb | 1 + zjit/src/codegen.rs | 8 ++++++-- zjit/src/stats.rs | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/zjit.rb b/zjit.rb index 5e647bfbeeeb89..d6f7a4069267b8 100644 --- a/zjit.rb +++ b/zjit.rb @@ -44,6 +44,7 @@ def stats_string print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) # Show the most important stats ratio_in_zjit at the end print_counters([ diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2a581d8baddac8..1f71ad9db3b2b8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -370,7 +370,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio gen_send_without_block(jit, asm, *cd, &function.frame_state(*state)), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state)), - Insn::InvokeBlock { cd, state, .. } => gen_invoke_block(jit, asm, *cd, &function.frame_state(*state)), + Insn::InvokeBlock { cd, state, .. } => gen_invokeblock(jit, asm, *cd, &function.frame_state(*state)), // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), @@ -981,6 +981,7 @@ fn gen_send( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_send); // Save PC and SP gen_prepare_call_with_gc(asm, state); @@ -1008,6 +1009,7 @@ fn gen_send_without_block( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_send_without_block); // Note that it's incorrect to use this frame state to side exit because // the state might not be on the boundary of an interpreter instruction. @@ -1108,13 +1110,14 @@ fn gen_send_without_block_direct( } /// Compile for invokeblock -fn gen_invoke_block( +fn gen_invokeblock( jit: &mut JITState, asm: &mut Assembler, cd: *const rb_call_data, state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_invokeblock); // Save PC and SP, spill locals and stack gen_prepare_call_with_gc(asm, state); @@ -1141,6 +1144,7 @@ fn gen_invokesuper( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::dynamic_send_count); + gen_incr_counter(asm, Counter::dynamic_send_type_invokesuper); // Save PC and SP gen_prepare_call_with_gc(asm, state); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 98ddc20226621b..39cfe6a4398fb3 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -142,6 +142,10 @@ make_counters! { // The number of times we do a dynamic dispatch from JIT code dynamic_send_count, + dynamic_send_type_send_without_block, + dynamic_send_type_send, + dynamic_send_type_invokeblock, + dynamic_send_type_invokesuper, } /// Increase a counter by a specified amount From dd3aa0a52c564624a1e15b4447c3dc291304dda5 Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 11:56:05 +0200 Subject: [PATCH 032/104] [ruby/prism] Add field documentation for MatchRequiredNode https://github.com/ruby/prism/commit/03ca35b3ab --- prism/config.yml | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index b37b98cbdfe252..54538300085b6f 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -3478,11 +3478,65 @@ nodes: - name: value type: node kind: non-void expression + comment: | + Represents the left-hand side of the operator. + + foo => bar + ^^^ - name: pattern type: node kind: pattern expression + comment: | + Represents the right-hand side of the operator. The type of the node depends on the expression. + + Anything that looks like a local variable name (including `_`) will result in a `LocalVariableTargetNode`. + + foo => a # This is equivalent to writing `a = foo` + ^ + + Using an explicit `Array` or combining expressions with `,` will result in a `ArrayPatternNode`. This can be preceded by a constant. + + foo => [a] + ^^^ + + foo => a, b + ^^^^ + + foo => Bar[a, b] + ^^^^^^^^^ + + If the array pattern contains at least two wildcard matches, a `FindPatternNode` is created instead. + + foo => *, 1, *a + ^^^^^ + + Using an explicit `Hash` or a constant with square brackets and hash keys in the square brackets will result in a `HashPatternNode`. + + foo => { a: 1, b: } + + foo => Bar[a: 1, b:] + + foo => Bar[**] + + To use any variable that needs run time evaluation, pinning is required. This results in a `PinnedVariableNode` + + foo => ^a + ^^ + + Similar, any expression can be used with pinning. This results in a `PinnedExpressionNode`. + + foo => ^(a + 1) + + Anything else will result in the regular node for that expression, for example a `ConstantReadNode`. + + foo => CONST - name: operator_loc type: location + comment: | + The location of the operator. + + foo => bar + ^^ comment: | Represents the use of the `=>` operator. From 9fddf5a032d6c3b7982fc0f7213c008e9bc17abc Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 11:58:02 +0200 Subject: [PATCH 033/104] [ruby/prism] Add pattern match documentation example to LocalVariableTargetNode https://github.com/ruby/prism/commit/193984b760 --- prism/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index 54538300085b6f..923e535ba9328c 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -3388,6 +3388,9 @@ nodes: foo, bar = baz ^^^ ^^^ + + foo => baz + ^^^ - name: LocalVariableWriteNode fields: - name: name From 8efa67034947352f32e3c56aa6338eea6611c8b7 Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 13:37:05 +0200 Subject: [PATCH 034/104] [ruby/prism] Add field documentation for ArrayPatternNode https://github.com/ruby/prism/commit/c80c4d958e --- prism/config.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index 923e535ba9328c..f2e86a497cfc76 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -992,8 +992,19 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the Array + + foo in Bar[] + ^^^ + + foo in Bar[1, 2, 3] + ^^^ + + foo in Bar::Baz[1, 2, 3] + ^^^^^^^^ - name: requireds type: node[] kind: pattern expression From 22702a1e3b5d9789360b3fc72ca5c82ddff38828 Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 14:07:55 +0200 Subject: [PATCH 035/104] [ruby/prism] Add field documentation for HashPatternNode https://github.com/ruby/prism/commit/9b7dfcc3e0 --- prism/config.yml | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index f2e86a497cfc76..16754318b2bfc6 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2725,20 +2725,60 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the Hash. + + foo => Bar[a: 1, b: 2] + ^^^ + + foo => Bar::Baz[a: 1, b: 2] + ^^^^^^^^ - name: elements type: node[] kind: AssocNode + comment: | + Represents the explicit named hash keys and values. + + foo => { a: 1, b:, ** } + ^^^^^^^^ - name: rest type: node? kind: - AssocSplatNode - NoKeywordsParameterNode + comment: | + Represents the rest of the Hash keys and values. This can be named, unnamed, or explicitly forbidden via `**nil`, this last one results in a `NoKeywordsParameterNode`. + + foo => { a: 1, b:, **c } + ^^^ + + foo => { a: 1, b:, ** } + ^^ + + foo => { a: 1, b:, **nil } + ^^^^^ - name: opening_loc type: location? + comment: | + The location of the opening brace. + + foo => { a: 1 } + ^ + + foo => Bar[a: 1] + ^ - name: closing_loc type: location? + comment: | + The location of the closing brace. + + foo => { a: 1 } + ^ + + foo => Bar[a: 1] + ^ comment: | Represents a hash pattern in pattern matching. @@ -2747,6 +2787,12 @@ nodes: foo => { a: 1, b: 2, **c } ^^^^^^^^^^^^^^^^^^^ + + foo => Bar[a: 1, b: 2] + ^^^^^^^^^^^^^^^ + + foo in { a: 1, b: 2 } + ^^^^^^^^^^^^^^ - name: IfNode fields: - name: if_keyword_loc From bfd6da7448dbb32916d9802308fe2c136ae58e9a Mon Sep 17 00:00:00 2001 From: Herwin Date: Fri, 28 Jun 2024 14:16:35 +0200 Subject: [PATCH 036/104] [ruby/prism] Add field documentation for FindPatternNode https://github.com/ruby/prism/commit/a0cc316e91 --- prism/config.yml | 50 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index 16754318b2bfc6..de78d654622ac7 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2420,23 +2420,68 @@ nodes: - name: constant type: node? kind: - - ConstantReadNode - ConstantPathNode + - ConstantReadNode + comment: | + Represents the optional constant preceding the pattern + + foo in Foo(*bar, baz, *qux) + ^^^ - name: left type: node kind: SplatNode + comment: | + Represents the first wildcard node in the pattern. + + foo in *bar, baz, *qux + ^^^^ + + foo in Foo(*bar, baz, *qux) + ^^^^ - name: requireds type: node[] kind: pattern expression + comment: | + Represents the nodes in between the wildcards. + + foo in *bar, baz, *qux + ^^^ + + foo in Foo(*bar, baz, 1, *qux) + ^^^^^^ - name: right type: node kind: - SplatNode - on error: MissingNode + comment: | + Represents the second wildcard node in the pattern. + + foo in *bar, baz, *qux + ^^^^ + + foo in Foo(*bar, baz, *qux) + ^^^^ - name: opening_loc type: location? + comment: | + The location of the openingbrace. + + foo in [*bar, baz, *qux] + ^ + + foo in Foo(*bar, baz, *qux) + ^ - name: closing_loc type: location? + comment: | + The location of the closing brace. + + foo in [*bar, baz, *qux] + ^ + + foo in Foo(*bar, baz, *qux) + ^ comment: | Represents a find pattern in pattern matching. @@ -2448,6 +2493,9 @@ nodes: foo in Foo(*bar, baz, *qux) ^^^^^^^^^^^^^^^^^^^^ + + foo => *bar, baz, *qux + ^^^^^^^^^^^^^^^ - name: FlipFlopNode flags: RangeFlags fields: From aaeeac441eb88d59631a48f808ccce59a9c30b86 Mon Sep 17 00:00:00 2001 From: Herwin Date: Sat, 29 Jun 2024 17:17:16 +0200 Subject: [PATCH 037/104] [ruby/prism] Add field documentation for PinnedVariableNode https://github.com/ruby/prism/commit/af9047f378 --- prism/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index de78d654622ac7..c53265b3507532 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -4098,8 +4098,18 @@ nodes: - NumberedReferenceReadNode # foo in ^$1 - ItLocalVariableReadNode # proc { 1 in ^it } - on error: MissingNode # foo in ^Bar + comment: | + The variable used in the pinned expression + + foo in ^bar + ^^^ - name: operator_loc type: location + comment: | + The location of the `^` operator + + foo in ^bar + ^ comment: | Represents the use of the `^` operator for pinning a variable in a pattern matching expression. From dcfd98b00537e53505c6be8e53cba01ad22ba416 Mon Sep 17 00:00:00 2001 From: Herwin Date: Tue, 2 Jul 2024 16:56:38 +0200 Subject: [PATCH 038/104] [ruby/prism] Add field documentation for PinnedExpressionNode https://github.com/ruby/prism/commit/0d94291416 --- prism/config.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index c53265b3507532..3366b6235d003c 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2465,7 +2465,7 @@ nodes: - name: opening_loc type: location? comment: | - The location of the openingbrace. + The location of the opening brace. foo in [*bar, baz, *qux] ^ @@ -4074,12 +4074,32 @@ nodes: - name: expression type: node kind: non-void expression + comment: | + The expression used in the pinned expression + + foo in ^(bar) + ^^^ - name: operator_loc type: location + comment: | + The location of the `^` operator + + foo in ^(bar) + ^ - name: lparen_loc type: location + comment: | + The location of the opening parenthesis. + + foo in ^(bar) + ^ - name: rparen_loc type: location + comment: | + The location of the closing parenthesis. + + foo in ^(bar) + ^ comment: | Represents the use of the `^` operator for pinning an expression in a pattern matching expression. From 0803b9a6acd6634fd24ec9cc53725eafbaccb856 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Mon, 16 Sep 2024 09:45:23 -0400 Subject: [PATCH 039/104] [ruby/prism] Document lifetime of `pm_options_t` https://github.com/ruby/prism/commit/ed8f6307c1 --- prism/prism.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/prism/prism.h b/prism/prism.h index a6f22f1a5ad39f..11e854eb35b502 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -49,10 +49,13 @@ PRISM_EXPORTED_FUNCTION const char * pm_version(void); /** * Initialize a parser with the given start and end pointers. * + * The resulting parser must eventually be freed with `pm_parser_free()`. + * * @param parser The parser to initialize. * @param source The source to parse. * @param size The size of the source. - * @param options The optional options to use when parsing. + * @param options The optional options to use when parsing. These options must + * live for the whole lifetime of this parser. */ PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options); @@ -68,6 +71,9 @@ PRISM_EXPORTED_FUNCTION void pm_parser_register_encoding_changed_callback(pm_par /** * Free any memory associated with the given parser. * + * This does not free the `pm_options_t` object that was used to initialize the + * parser. + * * @param parser The parser to free. */ PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser); From 2c9afcc3896c9c18f6ea8278f479a379401551a1 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 21 Dec 2024 15:36:03 -0500 Subject: [PATCH 040/104] [ruby/prism] Support leading logical operators https://github.com/ruby/prism/commit/3f58fa7705 --- prism/prism.c | 94 +++++++++++++++++++++++-- test/prism/fixtures/leading_logical.txt | 21 ++++++ test/prism/fixtures_test.rb | 2 + test/prism/lex_test.rb | 3 + test/prism/ruby/parser_test.rb | 3 + test/prism/ruby/ripper_test.rb | 3 + test/prism/ruby/ruby_parser_test.rb | 1 + 7 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 test/prism/fixtures/leading_logical.txt diff --git a/prism/prism.c b/prism/prism.c index 2f7ce0b9866438..daf69d2ef97553 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -10834,14 +10834,37 @@ parser_lex(pm_parser_t *parser) { following = next_newline(following, parser->end - following); } - // If the lex state was ignored, or we hit a '.' or a '&.', - // we will lex the ignored newline + // If the lex state was ignored, we will lex the + // ignored newline. + if (lex_state_ignored_p(parser)) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lexed_comment = false; + goto lex_next_token; + } + + // If we hit a '.' or a '&.' we will lex the ignored + // newline. + if (following && ( + (peek_at(parser, following) == '.') || + (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.') + )) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lexed_comment = false; + goto lex_next_token; + } + + + // If we are parsing as CRuby 3.5 or later and we + // hit a '&&' or a '||' then we will lex the ignored + // newline. if ( - lex_state_ignored_p(parser) || - (following && ( - (peek_at(parser, following) == '.') || - (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.') - )) + (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) && + following && ( + (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || + (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || + (peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd' && !char_is_identifier(parser, following + 3, parser->end - (following + 3))) || + (peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2))) + ) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); lexed_comment = false; @@ -10881,6 +10904,63 @@ parser_lex(pm_parser_t *parser) { parser->next_start = NULL; LEX(PM_TOKEN_AMPERSAND_DOT); } + + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + // If we hit an && then we are in a logical chain + // and we need to return the logical operator. + if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + LEX(PM_TOKEN_AMPERSAND_AMPERSAND); + } + + // If we hit a || then we are in a logical chain and + // we need to return the logical operator. + if (peek_at(parser, next_content) == '|' && peek_at(parser, next_content + 1) == '|') { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + LEX(PM_TOKEN_PIPE_PIPE); + } + + // If we hit an 'and' then we are in a logical chain + // and we need to return the logical operator. + if ( + peek_at(parser, next_content) == 'a' && + peek_at(parser, next_content + 1) == 'n' && + peek_at(parser, next_content + 2) == 'd' && + !char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3)) + ) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 3; + parser->next_start = NULL; + parser->command_start = true; + LEX(PM_TOKEN_KEYWORD_AND); + } + + // If we hit a 'or' then we are in a logical chain + // and we need to return the logical operator. + if ( + peek_at(parser, next_content) == 'o' && + peek_at(parser, next_content + 1) == 'r' && + !char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2)) + ) { + if (!lexed_comment) parser_lex_ignored_newline(parser); + lex_state_set(parser, PM_LEX_STATE_BEG); + parser->current.start = next_content; + parser->current.end = next_content + 2; + parser->next_start = NULL; + parser->command_start = true; + LEX(PM_TOKEN_KEYWORD_OR); + } + } } // At this point we know this is a regular newline, and we can set the diff --git a/test/prism/fixtures/leading_logical.txt b/test/prism/fixtures/leading_logical.txt new file mode 100644 index 00000000000000..feb5ee245c8b2f --- /dev/null +++ b/test/prism/fixtures/leading_logical.txt @@ -0,0 +1,21 @@ +1 +&& 2 +&& 3 + +1 +|| 2 +|| 3 + +1 +and 2 +and 3 + +1 +or 2 +or 3 + +1 +andfoo + +2 +orfoo diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 3b4a502b9010f1..124a834317498b 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -25,6 +25,8 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + except << "leading_logical.txt" if RUBY_VERSION < "3.5.0" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_valid_syntax(fixture.read) } end diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index d34c3d9dd3cb2f..abce18a0ad3387 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -42,6 +42,9 @@ class LexTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + # https://bugs.ruby-lang.org/issues/20925 + except << "leading_logical.txt" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_lex(fixture) } end diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 2a4ea981bdb5c3..129c38a3b5b40d 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -64,6 +64,9 @@ class ParserTest < TestCase # 1.. && 2 "ranges.txt", + + # Cannot yet handling leading logical operators. + "leading_logical.txt", ] # These files contain code that is being parsed incorrectly by the parser diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 5c37178889ecf5..637202487811b5 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -8,6 +8,9 @@ module Prism class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ + # Not yet supported. + "leading_logical.txt", + # Ripper incorrectly attributes the block to the keyword. "seattlerb/block_break.txt", "seattlerb/block_next.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 960e7f63e46385..f4f0f331fb89a9 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -38,6 +38,7 @@ class RubyParserTest < TestCase "dos_endings.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt", + "leading_logical.txt", "method_calls.txt", "methods.txt", "multi_write.txt", From a9b35b3a0777e299b27d039b4b09fe8bdc9984c8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 12 Sep 2025 09:32:30 -0400 Subject: [PATCH 041/104] Remove useless field in rb_imemo_tmpbuf_t --- internal/imemo.h | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/imemo.h b/internal/imemo.h index f7bd6202384dca..de39102432ea84 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -93,7 +93,6 @@ struct vm_ifunc { struct rb_imemo_tmpbuf_struct { VALUE flags; - VALUE reserved; VALUE *ptr; /* malloc'ed buffer */ struct rb_imemo_tmpbuf_struct *next; /* next imemo */ size_t cnt; /* buffer size in VALUE */ From 120d3b12a9981f547b07c937dd183c0abe77c6cb Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Fri, 30 Aug 2024 15:02:01 -0400 Subject: [PATCH 042/104] [ruby/prism] Add links to code refs in docs https://github.com/ruby/prism/commit/d2d9a1f1a7 --- prism/prism.h | 20 ++++++++++---------- prism/util/pm_string.h | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/prism/prism.h b/prism/prism.h index 11e854eb35b502..555bda08515fa9 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -322,10 +322,10 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * to want to use and be aware of are: * * * `pm_parser_t` - the main parser structure - * * `pm_parser_init` - initialize a parser - * * `pm_parse` - parse and return the root node - * * `pm_node_destroy` - deallocate the root node returned by `pm_parse` - * * `pm_parser_free` - free the internal memory of the parser + * * `pm_parser_init()` - initialize a parser + * * `pm_parse()` - parse and return the root node + * * `pm_node_destroy()` - deallocate the root node returned by `pm_parse()` + * * `pm_parser_free()` - free the internal memory of the parser * * Putting all of this together would look something like: * @@ -342,8 +342,8 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * } * ``` * - * All of the nodes "inherit" from `pm_node_t` by embedding those structures as - * their first member. This means you can downcast and upcast any node in the + * All of the nodes "inherit" from `pm_node_t` by embedding those structures + * as their first member. This means you can downcast and upcast any node in the * tree to a `pm_node_t`. * * @section serializing Serializing @@ -355,9 +355,9 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * use and be aware of are: * * * `pm_buffer_t` - a small buffer object that will hold the serialized AST - * * `pm_buffer_free` - free the memory associated with the buffer - * * `pm_serialize` - serialize the AST into a buffer - * * `pm_serialize_parse` - parse and serialize the AST into a buffer + * * `pm_buffer_free()` - free the memory associated with the buffer + * * `pm_serialize()` - serialize the AST into a buffer + * * `pm_serialize_parse()` - parse and serialize the AST into a buffer * * Putting all of this together would look something like: * @@ -375,7 +375,7 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * @section inspecting Inspecting * * Prism provides the ability to inspect the AST by pretty-printing nodes. You - * can do this with the `pm_prettyprint` function, which you would use like: + * can do this with the `pm_prettyprint()` function, which you would use like: * * ```c * void prettyprint(const uint8_t *source, size_t length) { diff --git a/prism/util/pm_string.h b/prism/util/pm_string.h index f99f1abdf38c9b..ccf6648d263bc0 100644 --- a/prism/util/pm_string.h +++ b/prism/util/pm_string.h @@ -45,11 +45,11 @@ typedef struct { /** This is a slice of another string, and should not be freed. */ PM_STRING_SHARED, - /** This string owns its memory, and should be freed using `pm_string_free`. */ + /** This string owns its memory, and should be freed using `pm_string_free()`. */ PM_STRING_OWNED, #ifdef PRISM_HAS_MMAP - /** This string is a memory-mapped file, and should be freed using `pm_string_free`. */ + /** This string is a memory-mapped file, and should be freed using `pm_string_free()`. */ PM_STRING_MAPPED #endif } type; From f2dbc4ec82a0e103ac1e3f64f5983540cdc75fd3 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:29:35 +0200 Subject: [PATCH 043/104] [ruby/prism] [Bug #17398] Allow `private def hello = puts "Hello"` This was a limitation of parse.y that prism intentionally replicated. https://github.com/ruby/prism/commit/8fd12d594c --- prism/prism.c | 14 +++++++------- test/prism/errors/endless_method_command_call.txt | 3 +++ test/prism/errors/private_endless_method.txt | 3 --- test/prism/errors_test.rb | 9 +++++++++ .../fixtures/endless_methods_command_call.txt | 8 ++++++++ test/prism/fixtures_test.rb | 5 +++-- test/prism/lex_test.rb | 3 +++ test/prism/locals_test.rb | 6 +++++- test/prism/ruby/parser_test.rb | 3 +++ test/prism/ruby/ripper_test.rb | 5 ++++- test/prism/ruby/ruby_parser_test.rb | 5 ++++- 11 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 test/prism/errors/endless_method_command_call.txt delete mode 100644 test/prism/errors/private_endless_method.txt create mode 100644 test/prism/fixtures/endless_methods_command_call.txt diff --git a/prism/prism.c b/prism/prism.c index daf69d2ef97553..337b77637b748f 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19585,13 +19585,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_do_loop_stack_push(parser, false); statements = (pm_node_t *) pm_statements_node_create(parser); - // In endless method bodies, we need to handle command calls carefully. - // We want to allow command calls in assignment context but maintain - // the same binding power to avoid changing how operators are parsed. - // Note that we're intentionally NOT allowing code like `private def foo = puts "Hello"` - // because the original parser, parse.y, can't handle it and we want to maintain the same behavior - bool allow_command_call = (binding_power == PM_BINDING_POWER_ASSIGNMENT) || - (binding_power < PM_BINDING_POWER_COMPOSITION); + bool allow_command_call; + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + allow_command_call = accepts_command_call; + } else { + // Allow `def foo = puts "Hello"` but not `private def foo = puts "Hello"` + allow_command_call = binding_power == PM_BINDING_POWER_ASSIGNMENT || binding_power < PM_BINDING_POWER_COMPOSITION; + } pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, allow_command_call, false, PM_ERR_DEF_ENDLESS, (uint16_t) (depth + 1)); diff --git a/test/prism/errors/endless_method_command_call.txt b/test/prism/errors/endless_method_command_call.txt new file mode 100644 index 00000000000000..e6a328c2944a68 --- /dev/null +++ b/test/prism/errors/endless_method_command_call.txt @@ -0,0 +1,3 @@ +private :m, def hello = puts "Hello" + ^ unexpected string literal, expecting end-of-input + diff --git a/test/prism/errors/private_endless_method.txt b/test/prism/errors/private_endless_method.txt deleted file mode 100644 index 8aae5e0cd39035..00000000000000 --- a/test/prism/errors/private_endless_method.txt +++ /dev/null @@ -1,3 +0,0 @@ -private def foo = puts "Hello" - ^ unexpected string literal, expecting end-of-input - diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 62bbd8458b2ca9..9dd4aea72865d9 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -100,6 +100,15 @@ def foo(bar: bar) = 42 end end + def test_private_endless_method + source = <<~RUBY + private def foo = puts "Hello" + RUBY + + assert_predicate Prism.parse(source, version: "3.4"), :failure? + assert_predicate Prism.parse(source), :success? + end + private def assert_errors(filepath) diff --git a/test/prism/fixtures/endless_methods_command_call.txt b/test/prism/fixtures/endless_methods_command_call.txt new file mode 100644 index 00000000000000..91a9d156d5538b --- /dev/null +++ b/test/prism/fixtures/endless_methods_command_call.txt @@ -0,0 +1,8 @@ +private def foo = puts "Hello" +private def foo = puts "Hello", "World" +private def foo = puts "Hello" do expr end +private def foo() = puts "Hello" +private def foo(x) = puts x +private def obj.foo = puts "Hello" +private def obj.foo() = puts "Hello" +private def obj.foo(x) = puts x diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 124a834317498b..b4b656fcf49b48 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -8,7 +8,6 @@ module Prism class FixturesTest < TestCase except = [] - if RUBY_VERSION < "3.3.0" # Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace # characters in the heredoc start. @@ -25,7 +24,9 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end - except << "leading_logical.txt" if RUBY_VERSION < "3.5.0" + # Leaving these out until they are supported by parse.y. + except << "leading_logical.txt" + except << "endless_methods_command_call.txt" Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_valid_syntax(fixture.read) } diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index abce18a0ad3387..4eacbab3e1170d 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -45,6 +45,9 @@ class LexTest < TestCase # https://bugs.ruby-lang.org/issues/20925 except << "leading_logical.txt" + # https://bugs.ruby-lang.org/issues/17398#note-12 + except << "endless_methods_command_call.txt" + Fixture.each(except: except) do |fixture| define_method(fixture.test_name) { assert_lex(fixture) } end diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index e0e9a458559759..950e7118af526a 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -29,7 +29,11 @@ class LocalsTest < TestCase except = [ # Skip this fixture because it has a different number of locals because # CRuby is eliminating dead code. - "whitequark/ruby_bug_10653.txt" + "whitequark/ruby_bug_10653.txt", + + # Leaving these out until they are supported by parse.y. + "leading_logical.txt", + "endless_methods_command_call.txt" ] Fixture.each(except: except) do |fixture| diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 129c38a3b5b40d..98740f09734043 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -67,6 +67,9 @@ class ParserTest < TestCase # Cannot yet handling leading logical operators. "leading_logical.txt", + + # Ruby >= 3.5 specific syntax + "endless_methods_command_call.txt", ] # These files contain code that is being parsed incorrectly by the parser diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 637202487811b5..39325137ba07f2 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -29,7 +29,10 @@ class RipperTest < TestCase "whitequark/lvar_injecting_match.txt", # Ripper fails to understand some structures that span across heredocs. - "spanning_heredoc.txt" + "spanning_heredoc.txt", + + # https://bugs.ruby-lang.org/issues/17398#note-12 + "endless_methods_command_call.txt", ] # Skip these tests that we haven't implemented yet. diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index f4f0f331fb89a9..bcaed7979150bc 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -74,7 +74,10 @@ class RubyParserTest < TestCase "whitequark/ruby_bug_11989.txt", "whitequark/ruby_bug_18878.txt", "whitequark/ruby_bug_19281.txt", - "whitequark/slash_newline_in_heredocs.txt" + "whitequark/slash_newline_in_heredocs.txt", + + # Ruby >= 3.5 specific syntax + "endless_methods_command_call.txt", ] Fixture.each(except: failures) do |fixture| From 043ff370c1f16e69a55b38250528cbcf263b3567 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Sep 2025 14:07:28 -0400 Subject: [PATCH 044/104] Update test syntax to handle command call endless methods --- test/.excludes-parsey/TestSyntax.rb | 1 + test/ruby/test_syntax.rb | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 test/.excludes-parsey/TestSyntax.rb diff --git a/test/.excludes-parsey/TestSyntax.rb b/test/.excludes-parsey/TestSyntax.rb new file mode 100644 index 00000000000000..ff7307060dd2a4 --- /dev/null +++ b/test/.excludes-parsey/TestSyntax.rb @@ -0,0 +1 @@ +exclude(:test_methoddef_endless_command, "[Bug #17398]") diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index bbdeb876ec3eb0..7e2185be39d47c 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1794,15 +1794,12 @@ def test_methoddef_endless_command assert_equal("class ok", k.rescued("ok")) assert_equal("instance ok", k.new.rescued("ok")) - # Current technical limitation: cannot prepend "private" or something for command endless def - error = /(syntax error,|\^~*) unexpected string literal/ - error2 = /(syntax error,|\^~*) unexpected local variable or method/ - assert_syntax_error('private def foo = puts "Hello"', error) - assert_syntax_error('private def foo() = puts "Hello"', error) - assert_syntax_error('private def foo(x) = puts x', error2) - assert_syntax_error('private def obj.foo = puts "Hello"', error) - assert_syntax_error('private def obj.foo() = puts "Hello"', error) - assert_syntax_error('private def obj.foo(x) = puts x', error2) + assert_valid_syntax('private def foo = puts "Hello"') + assert_valid_syntax('private def foo() = puts "Hello"') + assert_valid_syntax('private def foo(x) = puts x') + assert_valid_syntax('private def obj.foo = puts "Hello"') + assert_valid_syntax('private def obj.foo() = puts "Hello"') + assert_valid_syntax('private def obj.foo(x) = puts x') end def test_methoddef_in_cond From 869c63bcc3fb8d518fcc5eea30b57299eb07fd03 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Fri, 30 Aug 2024 16:11:50 -0400 Subject: [PATCH 045/104] [ruby/prism] Add `\memberof` annotations https://github.com/ruby/prism/commit/d1d2161219 --- prism/options.h | 34 ++++++++++++++++++++++++++++++++++ prism/prism.h | 12 +++++++++++- prism/regexp.h | 4 ++-- prism/util/pm_buffer.h | 8 ++++++++ prism/util/pm_integer.h | 4 ++++ prism/util/pm_list.h | 6 ++++++ prism/util/pm_string.h | 10 ++++++++++ 7 files changed, 75 insertions(+), 3 deletions(-) diff --git a/prism/options.h b/prism/options.h index 092fda4f07878a..1a92c470f1ea7d 100644 --- a/prism/options.h +++ b/prism/options.h @@ -237,6 +237,8 @@ static const uint8_t PM_OPTIONS_COMMAND_LINE_X = 0x20; * @param shebang_callback The shebang callback to set. * @param shebang_callback_data Any additional data that should be passed along * to the callback. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_shebang_callback_set(pm_options_t *options, pm_options_shebang_callback_t shebang_callback, void *shebang_callback_data); @@ -245,6 +247,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_shebang_callback_set(pm_options_t *optio * * @param options The options struct to set the filepath on. * @param filepath The filepath to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_filepath_set(pm_options_t *options, const char *filepath); @@ -253,6 +257,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_filepath_set(pm_options_t *options, cons * * @param options The options struct to set the line on. * @param line The line to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_line_set(pm_options_t *options, int32_t line); @@ -261,6 +267,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_line_set(pm_options_t *options, int32_t * * @param options The options struct to set the encoding on. * @param encoding The encoding to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_encoding_set(pm_options_t *options, const char *encoding); @@ -269,6 +277,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_encoding_set(pm_options_t *options, cons * * @param options The options struct to set the encoding_locked value on. * @param encoding_locked The encoding_locked value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_encoding_locked_set(pm_options_t *options, bool encoding_locked); @@ -277,6 +287,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_encoding_locked_set(pm_options_t *option * * @param options The options struct to set the frozen string literal value on. * @param frozen_string_literal The frozen string literal value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_frozen_string_literal_set(pm_options_t *options, bool frozen_string_literal); @@ -285,6 +297,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_frozen_string_literal_set(pm_options_t * * * @param options The options struct to set the command line option on. * @param command_line The command_line value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_command_line_set(pm_options_t *options, uint8_t command_line); @@ -297,6 +311,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_command_line_set(pm_options_t *options, * @param version The version to set. * @param length The length of the version string. * @return Whether or not the version was parsed successfully. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const char *version, size_t length); @@ -305,6 +321,8 @@ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const * * @param options The options struct to set the main script value on. * @param main_script The main script value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, bool main_script); @@ -313,6 +331,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, b * * @param options The options struct to set the partial script value on. * @param partial_script The partial script value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_partial_script_set(pm_options_t *options, bool partial_script); @@ -321,6 +341,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_partial_script_set(pm_options_t *options * * @param options The options struct to set the freeze value on. * @param freeze The freeze value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_freeze_set(pm_options_t *options, bool freeze); @@ -330,6 +352,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_freeze_set(pm_options_t *options, bool f * @param options The options struct to initialize the scopes array on. * @param scopes_count The number of scopes to allocate. * @return Whether or not the scopes array was initialized successfully. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION bool pm_options_scopes_init(pm_options_t *options, size_t scopes_count); @@ -339,6 +363,8 @@ PRISM_EXPORTED_FUNCTION bool pm_options_scopes_init(pm_options_t *options, size_ * @param options The options struct to get the scope from. * @param index The index of the scope to get. * @return A pointer to the scope at the given index. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION const pm_options_scope_t * pm_options_scope_get(const pm_options_t *options, size_t index); @@ -349,6 +375,8 @@ PRISM_EXPORTED_FUNCTION const pm_options_scope_t * pm_options_scope_get(const pm * @param scope The scope struct to initialize. * @param locals_count The number of locals to allocate. * @return Whether or not the scope was initialized successfully. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION bool pm_options_scope_init(pm_options_scope_t *scope, size_t locals_count); @@ -358,6 +386,8 @@ PRISM_EXPORTED_FUNCTION bool pm_options_scope_init(pm_options_scope_t *scope, si * @param scope The scope struct to get the local from. * @param index The index of the local to get. * @return A pointer to the local at the given index. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION const pm_string_t * pm_options_scope_local_get(const pm_options_scope_t *scope, size_t index); @@ -366,6 +396,8 @@ PRISM_EXPORTED_FUNCTION const pm_string_t * pm_options_scope_local_get(const pm_ * * @param scope The scope struct to set the forwarding on. * @param forwarding The forwarding value to set. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding); @@ -373,6 +405,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_scope_forwarding_set(pm_options_scope_t * Free the internal memory associated with the options. * * @param options The options struct whose internal memory should be freed. + * + * \public \memberof pm_options */ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options); diff --git a/prism/prism.h b/prism/prism.h index 555bda08515fa9..dc31f26e786a5c 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -56,6 +56,8 @@ PRISM_EXPORTED_FUNCTION const char * pm_version(void); * @param size The size of the source. * @param options The optional options to use when parsing. These options must * live for the whole lifetime of this parser. + * + * \public \memberof pm_parser */ PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options); @@ -65,6 +67,8 @@ PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t * * * @param parser The parser to register the callback with. * @param callback The callback to register. + * + * \public \memberof pm_parser */ PRISM_EXPORTED_FUNCTION void pm_parser_register_encoding_changed_callback(pm_parser_t *parser, pm_encoding_changed_callback_t callback); @@ -75,6 +79,8 @@ PRISM_EXPORTED_FUNCTION void pm_parser_register_encoding_changed_callback(pm_par * parser. * * @param parser The parser to free. + * + * \public \memberof pm_parser */ PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser); @@ -83,11 +89,13 @@ PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser); * * @param parser The parser to use. * @return The AST representing the source. + * + * \public \memberof pm_parser */ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser); /** - * This function is used in pm_parse_stream to retrieve a line of input from a + * This function is used in pm_parse_stream() to retrieve a line of input from a * stream. It closely mirrors that of fgets so that fgets can be used as the * default implementation. */ @@ -110,6 +118,8 @@ typedef int (pm_parse_stream_feof_t)(void *stream); * @param stream_feof The function to use to determine if the stream has hit eof. * @param options The optional options to use when parsing. * @return The AST representing the source. + * + * \public \memberof pm_parser */ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options); diff --git a/prism/regexp.h b/prism/regexp.h index c0b3163e93b649..5366b5a5a0d359 100644 --- a/prism/regexp.h +++ b/prism/regexp.h @@ -17,12 +17,12 @@ #include /** - * This callback is called when a named capture group is found. + * This callback is called by pm_regexp_parse() when a named capture group is found. */ typedef void (*pm_regexp_name_callback_t)(const pm_string_t *name, void *data); /** - * This callback is called when a parse error is found. + * This callback is called by pm_regexp_parse() when a parse error is found. */ typedef void (*pm_regexp_error_callback_t)(const uint8_t *start, const uint8_t *end, const char *message, void *data); diff --git a/prism/util/pm_buffer.h b/prism/util/pm_buffer.h index f3c20ab2a5ad10..cb80f8b3ce7d01 100644 --- a/prism/util/pm_buffer.h +++ b/prism/util/pm_buffer.h @@ -51,6 +51,8 @@ bool pm_buffer_init_capacity(pm_buffer_t *buffer, size_t capacity); * * @param buffer The buffer to initialize. * @returns True if the buffer was initialized successfully, false otherwise. + * + * \public \memberof pm_buffer_t */ PRISM_EXPORTED_FUNCTION bool pm_buffer_init(pm_buffer_t *buffer); @@ -59,6 +61,8 @@ PRISM_EXPORTED_FUNCTION bool pm_buffer_init(pm_buffer_t *buffer); * * @param buffer The buffer to get the value of. * @returns The value of the buffer. + * + * \public \memberof pm_buffer_t */ PRISM_EXPORTED_FUNCTION char * pm_buffer_value(const pm_buffer_t *buffer); @@ -67,6 +71,8 @@ PRISM_EXPORTED_FUNCTION char * pm_buffer_value(const pm_buffer_t *buffer); * * @param buffer The buffer to get the length of. * @returns The length of the buffer. + * + * \public \memberof pm_buffer_t */ PRISM_EXPORTED_FUNCTION size_t pm_buffer_length(const pm_buffer_t *buffer); @@ -222,6 +228,8 @@ void pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size * Free the memory associated with the buffer. * * @param buffer The buffer to free. + * + * \public \memberof pm_buffer_t */ PRISM_EXPORTED_FUNCTION void pm_buffer_free(pm_buffer_t *buffer); diff --git a/prism/util/pm_integer.h b/prism/util/pm_integer.h index a9e2966703408b..304665e6205dea 100644 --- a/prism/util/pm_integer.h +++ b/prism/util/pm_integer.h @@ -112,6 +112,8 @@ void pm_integers_reduce(pm_integer_t *numerator, pm_integer_t *denominator); * * @param buffer The buffer to append the string to. * @param integer The integer to convert to a string. + * + * \public \memberof pm_integer_t */ PRISM_EXPORTED_FUNCTION void pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer); @@ -120,6 +122,8 @@ PRISM_EXPORTED_FUNCTION void pm_integer_string(pm_buffer_t *buffer, const pm_int * the integer exceeds the size of a single node in the linked list. * * @param integer The integer to free. + * + * \public \memberof pm_integer_t */ PRISM_EXPORTED_FUNCTION void pm_integer_free(pm_integer_t *integer); diff --git a/prism/util/pm_list.h b/prism/util/pm_list.h index 3512dee979aa52..f544bb2943d3ff 100644 --- a/prism/util/pm_list.h +++ b/prism/util/pm_list.h @@ -68,6 +68,8 @@ typedef struct { * * @param list The list to check. * @return True if the given list is empty, otherwise false. + * + * \public \memberof pm_list_t */ PRISM_EXPORTED_FUNCTION bool pm_list_empty_p(pm_list_t *list); @@ -76,6 +78,8 @@ PRISM_EXPORTED_FUNCTION bool pm_list_empty_p(pm_list_t *list); * * @param list The list to check. * @return The size of the list. + * + * \public \memberof pm_list_t */ PRISM_EXPORTED_FUNCTION size_t pm_list_size(pm_list_t *list); @@ -91,6 +95,8 @@ void pm_list_append(pm_list_t *list, pm_list_node_t *node); * Deallocate the internal state of the given list. * * @param list The list to free. + * + * \public \memberof pm_list_t */ PRISM_EXPORTED_FUNCTION void pm_list_free(pm_list_t *list); diff --git a/prism/util/pm_string.h b/prism/util/pm_string.h index ccf6648d263bc0..d8456ff2947eb8 100644 --- a/prism/util/pm_string.h +++ b/prism/util/pm_string.h @@ -130,6 +130,8 @@ typedef enum { * @param string The string to initialize. * @param filepath The filepath to read. * @return The success of the read, indicated by the value of the enum. + * + * \public \memberof pm_string_t */ PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_mapped_init(pm_string_t *string, const char *filepath); @@ -141,6 +143,8 @@ PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_mapped_init(pm_string_ * @param string The string to initialize. * @param filepath The filepath to read. * @return The success of the read, indicated by the value of the enum. + * + * \public \memberof pm_string_t */ PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_file_init(pm_string_t *string, const char *filepath); @@ -169,6 +173,8 @@ int pm_string_compare(const pm_string_t *left, const pm_string_t *right); * * @param string The string to get the length of. * @return The length of the string. + * + * \public \memberof pm_string_t */ PRISM_EXPORTED_FUNCTION size_t pm_string_length(const pm_string_t *string); @@ -177,6 +183,8 @@ PRISM_EXPORTED_FUNCTION size_t pm_string_length(const pm_string_t *string); * * @param string The string to get the start pointer of. * @return The start pointer of the string. + * + * \public \memberof pm_string_t */ PRISM_EXPORTED_FUNCTION const uint8_t * pm_string_source(const pm_string_t *string); @@ -184,6 +192,8 @@ PRISM_EXPORTED_FUNCTION const uint8_t * pm_string_source(const pm_string_t *stri * Free the associated memory of the given string. * * @param string The string to free. + * + * \public \memberof pm_string_t */ PRISM_EXPORTED_FUNCTION void pm_string_free(pm_string_t *string); From 1efccd5f5672c7ecbea231784f8876d12f7758b8 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Sep 2025 19:26:24 +0100 Subject: [PATCH 046/104] Exclude leading logical tests for parsey Prism implemented https://bugs.ruby-lang.org/issues/20925 but parse.y doesn't seem to support it yet and is failing related Prism tests on CI. This adds excludes for the tests that are failing. --- test/.excludes-parsey/Prism/FixturesTest.rb | 1 + test/.excludes-parsey/Prism/LocalsTest.rb | 1 + 2 files changed, 2 insertions(+) create mode 100644 test/.excludes-parsey/Prism/FixturesTest.rb create mode 100644 test/.excludes-parsey/Prism/LocalsTest.rb diff --git a/test/.excludes-parsey/Prism/FixturesTest.rb b/test/.excludes-parsey/Prism/FixturesTest.rb new file mode 100644 index 00000000000000..452ff4f5058746 --- /dev/null +++ b/test/.excludes-parsey/Prism/FixturesTest.rb @@ -0,0 +1 @@ +exclude(:"test_leading_logical.txt", "Requires Feature #20925 to be implemented on parse.y") diff --git a/test/.excludes-parsey/Prism/LocalsTest.rb b/test/.excludes-parsey/Prism/LocalsTest.rb new file mode 100644 index 00000000000000..452ff4f5058746 --- /dev/null +++ b/test/.excludes-parsey/Prism/LocalsTest.rb @@ -0,0 +1 @@ +exclude(:"test_leading_logical.txt", "Requires Feature #20925 to be implemented on parse.y") From f0578492add4af39244e5f4758136b6b921878ca Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Sep 2025 15:19:19 -0400 Subject: [PATCH 047/104] [ruby/prism] Bump to v1.5.0 https://github.com/ruby/prism/commit/194edab827 --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 2 +- prism/version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 4daa5113007086..74b9971a00a3e8 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.4.0" + spec.version = "1.5.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index 506da2fd6f079e..f4cb9c438d5ed8 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.4.0" +#define EXPECTED_PRISM_VERSION "1.5.0" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 104b60f4842015..a5909e15bb67fe 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -10,7 +10,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 4 + MINOR_VERSION = 5 # The patch version of prism that we are expecting to find in the serialized # strings. diff --git a/prism/version.h b/prism/version.h index 0a2a8c8fce5a25..697c7b5ad6f37e 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,7 +14,7 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 4 +#define PRISM_VERSION_MINOR 5 /** * The patch version of the Prism library as an int. @@ -24,6 +24,6 @@ /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.4.0" +#define PRISM_VERSION "1.5.0" #endif From d8bc3d813f0bcaca3de09784e28acf85298fcd38 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 12 Sep 2025 19:31:18 +0000 Subject: [PATCH 048/104] Update default gems list at f0578492add4af39244e5f4758136b [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index a4f3c723df79fd..4679ef9a289380 100644 --- a/NEWS.md +++ b/NEWS.md @@ -167,7 +167,7 @@ The following default gems are updated. * io-wait 0.3.2 * json 2.13.2 * optparse 0.7.0.dev.2 -* prism 1.4.0 +* prism 1.5.0 * psych 5.2.6 * resolv 0.6.2 * stringio 3.1.8.dev From eaf64af61e4cd26200bf5f293693721ba085442f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Sep 2025 12:55:03 -0700 Subject: [PATCH 049/104] ZJIT: Let fallbacks handle unknown call types (#14518) --- zjit/src/hir.rs | 46 +++++++++++++++++++++------------------------- zjit/src/stats.rs | 22 ++-------------------- 2 files changed, 23 insertions(+), 45 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4ade541922641f..3726d8ec0e44d5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2968,17 +2968,8 @@ fn compute_bytecode_info(iseq: *const rb_iseq_t) -> BytecodeInfo { #[derive(Debug, PartialEq, Clone, Copy)] pub enum CallType { - Splat, BlockArg, - Kwarg, - KwSplat, Tailcall, - Super, - Zsuper, - OptSend, - KwSplatMut, - SplatMut, - Forwarding, } #[derive(Clone, Debug, PartialEq)] @@ -2996,17 +2987,8 @@ fn num_locals(iseq: *const rb_iseq_t) -> usize { /// If we can't handle the type of send (yet), bail out. fn unknown_call_type(flag: u32) -> Result<(), CallType> { - if (flag & VM_CALL_KW_SPLAT_MUT) != 0 { return Err(CallType::KwSplatMut); } - if (flag & VM_CALL_ARGS_SPLAT_MUT) != 0 { return Err(CallType::SplatMut); } - if (flag & VM_CALL_ARGS_SPLAT) != 0 { return Err(CallType::Splat); } - if (flag & VM_CALL_KW_SPLAT) != 0 { return Err(CallType::KwSplat); } if (flag & VM_CALL_ARGS_BLOCKARG) != 0 { return Err(CallType::BlockArg); } - if (flag & VM_CALL_KWARG) != 0 { return Err(CallType::Kwarg); } if (flag & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } - if (flag & VM_CALL_SUPER) != 0 { return Err(CallType::Super); } - if (flag & VM_CALL_ZSUPER) != 0 { return Err(CallType::Zsuper); } - if (flag & VM_CALL_OPT_SEND) != 0 { return Err(CallType::OptSend); } - if (flag & VM_CALL_FORWARDING) != 0 { return Err(CallType::Forwarding); } Ok(()) } @@ -5126,7 +5108,9 @@ mod tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v6:ArrayExact = ToArray v1 - SideExit UnhandledCallType(Splat) + v8:BasicObject = SendWithoutBlock v0, :foo, v6 + CheckInterrupts + Return v8 "); } @@ -5151,7 +5135,9 @@ mod tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v5:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) + v7:BasicObject = SendWithoutBlock v0, :foo, v5 + CheckInterrupts + Return v7 "); } @@ -5163,7 +5149,9 @@ mod tests { assert_snapshot!(hir_string("test"), @r" fn test@:2: bb0(v0:BasicObject, v1:BasicObject): - SideExit UnhandledCallType(KwSplat) + v6:BasicObject = SendWithoutBlock v0, :foo, v1 + CheckInterrupts + Return v6 "); } @@ -5252,7 +5240,9 @@ mod tests { v13:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v14:Fixnum[1] = Const Value(1) v16:BasicObject = SendWithoutBlock v12, :core#hash_merge_ptr, v11, v13, v14 - SideExit UnhandledCallType(KwSplatMut) + v18:BasicObject = SendWithoutBlock v0, :foo, v16 + CheckInterrupts + Return v18 "); } @@ -5267,7 +5257,9 @@ mod tests { v6:ArrayExact = ToNewArray v1 v7:Fixnum[1] = Const Value(1) ArrayPush v6, v7 - SideExit UnhandledCallType(SplatMut) + v11:BasicObject = SendWithoutBlock v0, :foo, v6 + CheckInterrupts + Return v11 "); } @@ -7863,7 +7855,9 @@ mod opt_tests { fn test@:3: bb0(v0:BasicObject): v4:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) + v6:BasicObject = SendWithoutBlock v0, :foo, v4 + CheckInterrupts + Return v6 "); } @@ -7879,7 +7873,9 @@ mod opt_tests { fn test@:3: bb0(v0:BasicObject): v4:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) + v6:BasicObject = SendWithoutBlock v0, :foo, v4 + CheckInterrupts + Return v6 "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 39cfe6a4398fb3..8a7073dd977c94 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -114,17 +114,8 @@ make_counters! { } // unhanded_call_: Unhandled call types - unhandled_call_splat, unhandled_call_block_arg, - unhandled_call_kwarg, - unhandled_call_kw_splat, unhandled_call_tailcall, - unhandled_call_super, - unhandled_call_zsuper, - unhandled_call_optsend, - unhandled_call_kw_splat_mut, - unhandled_call_splat_mut, - unhandled_call_forwarding, // compile_error_: Compile error reasons compile_error_iseq_stack_too_large, @@ -176,17 +167,8 @@ pub fn exit_counter_ptr_for_call_type(call_type: crate::hir::CallType) -> *mut u use crate::hir::CallType::*; use crate::stats::Counter::*; let counter = match call_type { - Splat => unhandled_call_splat, - BlockArg => unhandled_call_block_arg, - Kwarg => unhandled_call_kwarg, - KwSplat => unhandled_call_kw_splat, - Tailcall => unhandled_call_tailcall, - Super => unhandled_call_super, - Zsuper => unhandled_call_zsuper, - OptSend => unhandled_call_optsend, - KwSplatMut => unhandled_call_kw_splat_mut, - SplatMut => unhandled_call_splat_mut, - Forwarding => unhandled_call_forwarding, + BlockArg => unhandled_call_block_arg, + Tailcall => unhandled_call_tailcall, }; counter_ptr(counter) } From d3cb347a40ef789f37f5a4723ecb3ada7e8605a9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Sep 2025 13:34:55 -0700 Subject: [PATCH 050/104] ZJIT: Share more code with YJIT in jit.c (#14520) * ZJIT: Share more code with YJIT in jit.c * Fix ZJIT references to JIT --- jit.c | 174 ++++++++++++++++++++++++++++++++ yjit.c | 173 -------------------------------- yjit/bindgen/src/main.rs | 12 +-- yjit/src/backend/arm64/mod.rs | 4 +- yjit/src/codegen.rs | 4 +- yjit/src/cruby_bindings.inc.rs | 18 ++-- yjit/src/virtualmem.rs | 6 +- zjit.c | 175 --------------------------------- zjit/bindgen/src/main.rs | 12 +-- zjit/src/backend/arm64/mod.rs | 4 +- zjit/src/cruby_bindings.inc.rs | 18 ++-- zjit/src/state.rs | 4 +- zjit/src/virtualmem.rs | 6 +- 13 files changed, 218 insertions(+), 392 deletions(-) diff --git a/jit.c b/jit.c index efecbef35455ca..f233a2f01f1c8e 100644 --- a/jit.c +++ b/jit.c @@ -533,6 +533,131 @@ for_each_iseq_i(void *vstart, void *vend, size_t stride, void *data) return 0; } +uint32_t +rb_jit_get_page_size(void) +{ +#if defined(_SC_PAGESIZE) + long page_size = sysconf(_SC_PAGESIZE); + if (page_size <= 0) rb_bug("jit: failed to get page size"); + + // 1 GiB limit. x86 CPUs with PDPE1GB can do this and anything larger is unexpected. + // Though our design sort of assume we have fine grained control over memory protection + // which require small page sizes. + if (page_size > 0x40000000l) rb_bug("jit page size too large"); + + return (uint32_t)page_size; +#else +#error "JIT supports POSIX only for now" +#endif +} + +#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE) +// Align the current write position to a multiple of bytes +static uint8_t * +align_ptr(uint8_t *ptr, uint32_t multiple) +{ + // Compute the pointer modulo the given alignment boundary + uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple; + + // If the pointer is already aligned, stop + if (rem == 0) + return ptr; + + // Pad the pointer by the necessary amount to align it + uint32_t pad = multiple - rem; + + return ptr + pad; +} +#endif + +// Address space reservation. Memory pages are mapped on an as needed basis. +// See the Rust mm module for details. +uint8_t * +rb_jit_reserve_addr_space(uint32_t mem_size) +{ +#ifndef _WIN32 + uint8_t *mem_block; + + // On Linux + #if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE) + uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE); + uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_jit_reserve_addr_space; + uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX; + // Align the requested address to page size + uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size); + + // Probe for addresses close to this function using MAP_FIXED_NOREPLACE + // to improve odds of being in range for 32-bit relative call instructions. + do { + mem_block = mmap( + req_addr, + mem_size, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, + -1, + 0 + ); + + // If we succeeded, stop + if (mem_block != MAP_FAILED) { + ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space"); + break; + } + + // -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux + // main_code_addr < heap_addr, and in case we are in a shared + // library mapped higher than the heap, downwards is still better + // since it's towards the end of the heap rather than the stack.) + req_addr -= 4 * 1024 * 1024; + } while (req_addr < probe_region_end); + + // On MacOS and other platforms + #else + // Try to map a chunk of memory as executable + mem_block = mmap( + (void *)rb_jit_reserve_addr_space, + mem_size, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0 + ); + #endif + + // Fallback + if (mem_block == MAP_FAILED) { + // Try again without the address hint (e.g., valgrind) + mem_block = mmap( + NULL, + mem_size, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0 + ); + + if (mem_block != MAP_FAILED) { + ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space:fallback"); + } + } + + // Check that the memory mapping was successful + if (mem_block == MAP_FAILED) { + perror("ruby: jit: mmap:"); + if(errno == ENOMEM) { + // No crash report if it's only insufficient memory + exit(EXIT_FAILURE); + } + rb_bug("mmap failed"); + } + + return mem_block; +#else + // Windows not supported for now + return NULL; +#endif +} + // Walk all ISEQs in the heap and invoke the callback - shared between YJIT and ZJIT void rb_jit_for_each_iseq(rb_iseq_callback callback, void *data) @@ -540,3 +665,52 @@ rb_jit_for_each_iseq(rb_iseq_callback callback, void *data) struct iseq_callback_data callback_data = { .callback = callback, .data = data }; rb_objspace_each_objects(for_each_iseq_i, (void *)&callback_data); } + +bool +rb_jit_mark_writable(void *mem_block, uint32_t mem_size) +{ + return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0; +} + +void +rb_jit_mark_executable(void *mem_block, uint32_t mem_size) +{ + // Do not call mprotect when mem_size is zero. Some platforms may return + // an error for it. https://github.com/Shopify/ruby/issues/450 + if (mem_size == 0) { + return; + } + if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) { + rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s", + mem_block, (unsigned long)mem_size, strerror(errno)); + } +} + +// Free the specified memory block. +bool +rb_jit_mark_unused(void *mem_block, uint32_t mem_size) +{ + // On Linux, you need to use madvise MADV_DONTNEED to free memory. + // We might not need to call this on macOS, but it's not really documented. + // We generally prefer to do the same thing on both to ease testing too. + madvise(mem_block, mem_size, MADV_DONTNEED); + + // On macOS, mprotect PROT_NONE seems to reduce RSS. + // We also call this on Linux to avoid executing unused pages. + return mprotect(mem_block, mem_size, PROT_NONE) == 0; +} + +// Invalidate icache for arm64. +// `start` is inclusive and `end` is exclusive. +void +rb_jit_icache_invalidate(void *start, void *end) +{ + // Clear/invalidate the instruction cache. Compiles to nothing on x86_64 + // but required on ARM before running freshly written code. + // On Darwin it's the same as calling sys_icache_invalidate(). +#ifdef __GNUC__ + __builtin___clear_cache(start, end); +#elif defined(__aarch64__) +#error No instruction cache clear available with this compiler on Aarch64! +#endif +} diff --git a/yjit.c b/yjit.c index ac218a084ca33f..57b09d73b0bceb 100644 --- a/yjit.c +++ b/yjit.c @@ -69,60 +69,12 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); // The "_yjit_" part is for trying to be informative. We might want different // suffixes for symbols meant for Rust and symbols meant for broader CRuby. -bool -rb_yjit_mark_writable(void *mem_block, uint32_t mem_size) -{ - return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0; -} - -void -rb_yjit_mark_executable(void *mem_block, uint32_t mem_size) -{ - // Do not call mprotect when mem_size is zero. Some platforms may return - // an error for it. https://github.com/Shopify/ruby/issues/450 - if (mem_size == 0) { - return; - } - if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) { - rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s", - mem_block, (unsigned long)mem_size, strerror(errno)); - } -} - -// Free the specified memory block. -bool -rb_yjit_mark_unused(void *mem_block, uint32_t mem_size) -{ - // On Linux, you need to use madvise MADV_DONTNEED to free memory. - // We might not need to call this on macOS, but it's not really documented. - // We generally prefer to do the same thing on both to ease testing too. - madvise(mem_block, mem_size, MADV_DONTNEED); - - // On macOS, mprotect PROT_NONE seems to reduce RSS. - // We also call this on Linux to avoid executing unused pages. - return mprotect(mem_block, mem_size, PROT_NONE) == 0; -} - long rb_yjit_array_len(VALUE a) { return rb_array_len(a); } -// `start` is inclusive and `end` is exclusive. -void -rb_yjit_icache_invalidate(void *start, void *end) -{ - // Clear/invalidate the instruction cache. Compiles to nothing on x86_64 - // but required on ARM before running freshly written code. - // On Darwin it's the same as calling sys_icache_invalidate(). -#ifdef __GNUC__ - __builtin___clear_cache(start, end); -#elif defined(__aarch64__) -#error No instruction cache clear available with this compiler on Aarch64! -#endif -} - # define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) // For a given raw_sample (frame), set the hash with the caller's @@ -217,131 +169,6 @@ rb_yjit_exit_locations_dict(VALUE *yjit_raw_samples, int *yjit_line_samples, int return result; } -uint32_t -rb_yjit_get_page_size(void) -{ -#if defined(_SC_PAGESIZE) - long page_size = sysconf(_SC_PAGESIZE); - if (page_size <= 0) rb_bug("yjit: failed to get page size"); - - // 1 GiB limit. x86 CPUs with PDPE1GB can do this and anything larger is unexpected. - // Though our design sort of assume we have fine grained control over memory protection - // which require small page sizes. - if (page_size > 0x40000000l) rb_bug("yjit page size too large"); - - return (uint32_t)page_size; -#else -#error "YJIT supports POSIX only for now" -#endif -} - -#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE) -// Align the current write position to a multiple of bytes -static uint8_t * -align_ptr(uint8_t *ptr, uint32_t multiple) -{ - // Compute the pointer modulo the given alignment boundary - uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple; - - // If the pointer is already aligned, stop - if (rem == 0) - return ptr; - - // Pad the pointer by the necessary amount to align it - uint32_t pad = multiple - rem; - - return ptr + pad; -} -#endif - -// Address space reservation. Memory pages are mapped on an as needed basis. -// See the Rust mm module for details. -uint8_t * -rb_yjit_reserve_addr_space(uint32_t mem_size) -{ -#ifndef _WIN32 - uint8_t *mem_block; - - // On Linux - #if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE) - uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE); - uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_yjit_reserve_addr_space; - uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX; - // Align the requested address to page size - uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size); - - // Probe for addresses close to this function using MAP_FIXED_NOREPLACE - // to improve odds of being in range for 32-bit relative call instructions. - do { - mem_block = mmap( - req_addr, - mem_size, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, - -1, - 0 - ); - - // If we succeeded, stop - if (mem_block != MAP_FAILED) { - ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_yjit_reserve_addr_space"); - break; - } - - // -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux - // main_code_addr < heap_addr, and in case we are in a shared - // library mapped higher than the heap, downwards is still better - // since it's towards the end of the heap rather than the stack.) - req_addr -= 4 * 1024 * 1024; - } while (req_addr < probe_region_end); - - // On MacOS and other platforms - #else - // Try to map a chunk of memory as executable - mem_block = mmap( - (void *)rb_yjit_reserve_addr_space, - mem_size, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, - 0 - ); - #endif - - // Fallback - if (mem_block == MAP_FAILED) { - // Try again without the address hint (e.g., valgrind) - mem_block = mmap( - NULL, - mem_size, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, - 0 - ); - - if (mem_block != MAP_FAILED) { - ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_yjit_reserve_addr_space:fallback"); - } - } - - // Check that the memory mapping was successful - if (mem_block == MAP_FAILED) { - perror("ruby: yjit: mmap:"); - if(errno == ENOMEM) { - // No crash report if it's only insufficient memory - exit(EXIT_FAILURE); - } - rb_bug("mmap failed"); - } - - return mem_block; -#else - // Windows not supported for now - return NULL; -#endif -} - // Is anyone listening for :c_call and :c_return event currently? bool rb_c_method_tracing_currently_enabled(const rb_execution_context_t *ec) diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index b62c637e1dbc4e..29b17346cd90ae 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -244,11 +244,11 @@ fn main() { .allowlist_function("rb_iseq_(get|set)_yjit_payload") .allowlist_function("rb_iseq_pc_at_idx") .allowlist_function("rb_iseq_opcode_at_pc") - .allowlist_function("rb_yjit_reserve_addr_space") - .allowlist_function("rb_yjit_mark_writable") - .allowlist_function("rb_yjit_mark_executable") - .allowlist_function("rb_yjit_mark_unused") - .allowlist_function("rb_yjit_get_page_size") + .allowlist_function("rb_jit_reserve_addr_space") + .allowlist_function("rb_jit_mark_writable") + .allowlist_function("rb_jit_mark_executable") + .allowlist_function("rb_jit_mark_unused") + .allowlist_function("rb_jit_get_page_size") .allowlist_function("rb_yjit_iseq_builtin_attrs") .allowlist_function("rb_yjit_iseq_inspect") .allowlist_function("rb_yjit_builtin_function") @@ -267,7 +267,7 @@ fn main() { .allowlist_function("rb_ENCODING_GET") .allowlist_function("rb_yjit_get_proc_ptr") .allowlist_function("rb_yjit_exit_locations_dict") - .allowlist_function("rb_yjit_icache_invalidate") + .allowlist_function("rb_jit_icache_invalidate") .allowlist_function("rb_optimized_call") .allowlist_function("rb_yjit_sendish_sp_pops") .allowlist_function("rb_yjit_invokeblock_sp_pops") diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 66e333f867d451..6fa8fef627df0c 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -98,7 +98,7 @@ fn emit_jmp_ptr_with_invalidation(cb: &mut CodeBlock, dst_ptr: CodePtr) { #[cfg(not(test))] { let end = cb.get_write_ptr(); - unsafe { rb_yjit_icache_invalidate(start.raw_ptr(cb) as _, end.raw_ptr(cb) as _) }; + unsafe { rb_jit_icache_invalidate(start.raw_ptr(cb) as _, end.raw_ptr(cb) as _) }; } } @@ -1361,7 +1361,7 @@ impl Assembler #[cfg(not(test))] cb.without_page_end_reserve(|cb| { for (start, end) in cb.writable_addrs(start_ptr, cb.get_write_ptr()) { - unsafe { rb_yjit_icache_invalidate(start as _, end as _) }; + unsafe { rb_jit_icache_invalidate(start as _, end as _) }; } }); diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 2bac85e62b57af..85fed25d24e48f 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -10945,7 +10945,7 @@ impl CodegenGlobals { #[cfg(not(test))] let (mut cb, mut ocb) = { - let virt_block: *mut u8 = unsafe { rb_yjit_reserve_addr_space(exec_mem_size as u32) }; + let virt_block: *mut u8 = unsafe { rb_jit_reserve_addr_space(exec_mem_size as u32) }; // Memory protection syscalls need page-aligned addresses, so check it here. Assuming // `virt_block` is page-aligned, `second_half` should be page-aligned as long as the @@ -10954,7 +10954,7 @@ impl CodegenGlobals { // // Basically, we don't support x86-64 2MiB and 1GiB pages. ARMv8 can do up to 64KiB // (2¹ⶠbytes) pages, which should be fine. 4KiB pages seem to be the most popular though. - let page_size = unsafe { rb_yjit_get_page_size() }; + let page_size = unsafe { rb_jit_get_page_size() }; assert_eq!( virt_block as usize % page_size.as_usize(), 0, "Start of virtual address block should be page-aligned", diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index a681d479770948..d9ef2d494f6d55 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1164,21 +1164,12 @@ extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); - pub fn rb_yjit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; - pub fn rb_yjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); - pub fn rb_yjit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; pub fn rb_yjit_array_len(a: VALUE) -> ::std::os::raw::c_long; - pub fn rb_yjit_icache_invalidate( - start: *mut ::std::os::raw::c_void, - end: *mut ::std::os::raw::c_void, - ); pub fn rb_yjit_exit_locations_dict( yjit_raw_samples: *mut VALUE, yjit_line_samples: *mut ::std::os::raw::c_int, samples_len: ::std::os::raw::c_int, ) -> VALUE; - pub fn rb_yjit_get_page_size() -> u32; - pub fn rb_yjit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_c_method_tracing_currently_enabled(ec: *const rb_execution_context_t) -> bool; pub fn rb_full_cfunc_return(ec: *mut rb_execution_context_t, return_value: VALUE); pub fn rb_iseq_get_yjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void; @@ -1325,5 +1316,14 @@ extern "C" { line: ::std::os::raw::c_int, ); pub fn rb_iseq_reset_jit_func(iseq: *const rb_iseq_t); + pub fn rb_jit_get_page_size() -> u32; + pub fn rb_jit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_jit_for_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); + pub fn rb_jit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; + pub fn rb_jit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); + pub fn rb_jit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; + pub fn rb_jit_icache_invalidate( + start: *mut ::std::os::raw::c_void, + end: *mut ::std::os::raw::c_void, + ); } diff --git a/yjit/src/virtualmem.rs b/yjit/src/virtualmem.rs index 97409c796cbca7..58c9d9a7fd380a 100644 --- a/yjit/src/virtualmem.rs +++ b/yjit/src/virtualmem.rs @@ -319,15 +319,15 @@ mod sys { impl super::Allocator for SystemAllocator { fn mark_writable(&mut self, ptr: *const u8, size: u32) -> bool { - unsafe { rb_yjit_mark_writable(ptr as VoidPtr, size) } + unsafe { rb_jit_mark_writable(ptr as VoidPtr, size) } } fn mark_executable(&mut self, ptr: *const u8, size: u32) { - unsafe { rb_yjit_mark_executable(ptr as VoidPtr, size) } + unsafe { rb_jit_mark_executable(ptr as VoidPtr, size) } } fn mark_unused(&mut self, ptr: *const u8, size: u32) -> bool { - unsafe { rb_yjit_mark_unused(ptr as VoidPtr, size) } + unsafe { rb_jit_mark_unused(ptr as VoidPtr, size) } } } } diff --git a/zjit.c b/zjit.c index 5d50775fc72c05..565a362a8a6aef 100644 --- a/zjit.c +++ b/zjit.c @@ -31,131 +31,6 @@ #include -uint32_t -rb_zjit_get_page_size(void) -{ -#if defined(_SC_PAGESIZE) - long page_size = sysconf(_SC_PAGESIZE); - if (page_size <= 0) rb_bug("zjit: failed to get page size"); - - // 1 GiB limit. x86 CPUs with PDPE1GB can do this and anything larger is unexpected. - // Though our design sort of assume we have fine grained control over memory protection - // which require small page sizes. - if (page_size > 0x40000000l) rb_bug("zjit page size too large"); - - return (uint32_t)page_size; -#else -#error "ZJIT supports POSIX only for now" -#endif -} - -#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE) -// Align the current write position to a multiple of bytes -static uint8_t * -align_ptr(uint8_t *ptr, uint32_t multiple) -{ - // Compute the pointer modulo the given alignment boundary - uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple; - - // If the pointer is already aligned, stop - if (rem == 0) - return ptr; - - // Pad the pointer by the necessary amount to align it - uint32_t pad = multiple - rem; - - return ptr + pad; -} -#endif - -// Address space reservation. Memory pages are mapped on an as needed basis. -// See the Rust mm module for details. -uint8_t * -rb_zjit_reserve_addr_space(uint32_t mem_size) -{ -#ifndef _WIN32 - uint8_t *mem_block; - - // On Linux - #if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE) - uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE); - uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_zjit_reserve_addr_space; - uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX; - // Align the requested address to page size - uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size); - - // Probe for addresses close to this function using MAP_FIXED_NOREPLACE - // to improve odds of being in range for 32-bit relative call instructions. - do { - mem_block = mmap( - req_addr, - mem_size, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, - -1, - 0 - ); - - // If we succeeded, stop - if (mem_block != MAP_FAILED) { - ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_zjit_reserve_addr_space"); - break; - } - - // -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux - // main_code_addr < heap_addr, and in case we are in a shared - // library mapped higher than the heap, downwards is still better - // since it's towards the end of the heap rather than the stack.) - req_addr -= 4 * 1024 * 1024; - } while (req_addr < probe_region_end); - - // On MacOS and other platforms - #else - // Try to map a chunk of memory as executable - mem_block = mmap( - (void *)rb_zjit_reserve_addr_space, - mem_size, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, - 0 - ); - #endif - - // Fallback - if (mem_block == MAP_FAILED) { - // Try again without the address hint (e.g., valgrind) - mem_block = mmap( - NULL, - mem_size, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, - 0 - ); - - if (mem_block != MAP_FAILED) { - ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_zjit_reserve_addr_space:fallback"); - } - } - - // Check that the memory mapping was successful - if (mem_block == MAP_FAILED) { - perror("ruby: zjit: mmap:"); - if(errno == ENOMEM) { - // No crash report if it's only insufficient memory - exit(EXIT_FAILURE); - } - rb_bug("mmap failed"); - } - - return mem_block; -#else - // Windows not supported for now - return NULL; -#endif -} - void rb_zjit_profile_disable(const rb_iseq_t *iseq); void @@ -185,56 +60,6 @@ rb_zjit_constcache_shareable(const struct iseq_inline_constant_cache_entry *ice) return (ice->flags & IMEMO_CONST_CACHE_SHAREABLE) != 0; } - -bool -rb_zjit_mark_writable(void *mem_block, uint32_t mem_size) -{ - return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0; -} - -void -rb_zjit_mark_executable(void *mem_block, uint32_t mem_size) -{ - // Do not call mprotect when mem_size is zero. Some platforms may return - // an error for it. https://github.com/Shopify/ruby/issues/450 - if (mem_size == 0) { - return; - } - if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) { - rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s", - mem_block, (unsigned long)mem_size, strerror(errno)); - } -} - -// Free the specified memory block. -bool -rb_zjit_mark_unused(void *mem_block, uint32_t mem_size) -{ - // On Linux, you need to use madvise MADV_DONTNEED to free memory. - // We might not need to call this on macOS, but it's not really documented. - // We generally prefer to do the same thing on both to ease testing too. - madvise(mem_block, mem_size, MADV_DONTNEED); - - // On macOS, mprotect PROT_NONE seems to reduce RSS. - // We also call this on Linux to avoid executing unused pages. - return mprotect(mem_block, mem_size, PROT_NONE) == 0; -} - -// Invalidate icache for arm64. -// `start` is inclusive and `end` is exclusive. -void -rb_zjit_icache_invalidate(void *start, void *end) -{ - // Clear/invalidate the instruction cache. Compiles to nothing on x86_64 - // but required on ARM before running freshly written code. - // On Darwin it's the same as calling sys_icache_invalidate(). -#ifdef __GNUC__ - __builtin___clear_cache(start, end); -#elif defined(__aarch64__) -#error No instruction cache clear available with this compiler on Aarch64! -#endif -} - // Convert a given ISEQ's instructions to zjit_* instructions void rb_zjit_profile_enable(const rb_iseq_t *iseq) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 26bdfd2848373f..7d66bf0ecf3f16 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -263,11 +263,11 @@ fn main() { .allowlist_function("rb_iseq_(get|set)_zjit_payload") .allowlist_function("rb_iseq_pc_at_idx") .allowlist_function("rb_iseq_opcode_at_pc") - .allowlist_function("rb_zjit_reserve_addr_space") - .allowlist_function("rb_zjit_mark_writable") - .allowlist_function("rb_zjit_mark_executable") - .allowlist_function("rb_zjit_mark_unused") - .allowlist_function("rb_zjit_get_page_size") + .allowlist_function("rb_jit_reserve_addr_space") + .allowlist_function("rb_jit_mark_writable") + .allowlist_function("rb_jit_mark_executable") + .allowlist_function("rb_jit_mark_unused") + .allowlist_function("rb_jit_get_page_size") .allowlist_function("rb_zjit_iseq_builtin_attrs") .allowlist_function("rb_zjit_iseq_inspect") .allowlist_function("rb_zjit_iseq_insn_set") @@ -280,7 +280,7 @@ fn main() { .allowlist_function("rb_RSTRING_LEN") .allowlist_function("rb_ENCODING_GET") .allowlist_function("rb_optimized_call") - .allowlist_function("rb_zjit_icache_invalidate") + .allowlist_function("rb_jit_icache_invalidate") .allowlist_function("rb_zjit_print_exception") .allowlist_function("rb_zjit_singleton_class_p") .allowlist_function("rb_zjit_defined_ivar") diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 6e040e5214f0c8..50e7074de11222 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -99,7 +99,7 @@ fn emit_jmp_ptr_with_invalidation(cb: &mut CodeBlock, dst_ptr: CodePtr) { let start = cb.get_write_ptr(); emit_jmp_ptr(cb, dst_ptr, true); let end = cb.get_write_ptr(); - unsafe { rb_zjit_icache_invalidate(start.raw_ptr(cb) as _, end.raw_ptr(cb) as _) }; + unsafe { rb_jit_icache_invalidate(start.raw_ptr(cb) as _, end.raw_ptr(cb) as _) }; } fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) { @@ -1382,7 +1382,7 @@ impl Assembler cb.link_labels(); // Invalidate icache for newly written out region so we don't run stale code. - unsafe { rb_zjit_icache_invalidate(start_ptr.raw_ptr(cb) as _, cb.get_write_ptr().raw_ptr(cb) as _) }; + unsafe { rb_jit_icache_invalidate(start_ptr.raw_ptr(cb) as _, cb.get_write_ptr().raw_ptr(cb) as _) }; Ok((start_ptr, gc_offsets)) } else { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index bab40e50ee32cc..9ee4b1bb743958 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -922,18 +922,9 @@ unsafe extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); - pub fn rb_zjit_get_page_size() -> u32; - pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t); pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; pub fn rb_zjit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool; - pub fn rb_zjit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; - pub fn rb_zjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); - pub fn rb_zjit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; - pub fn rb_zjit_icache_invalidate( - start: *mut ::std::os::raw::c_void, - end: *mut ::std::os::raw::c_void, - ); pub fn rb_zjit_iseq_insn_set( iseq: *const rb_iseq_t, insn_idx: ::std::os::raw::c_uint, @@ -1037,5 +1028,14 @@ unsafe extern "C" { line: ::std::os::raw::c_int, ); pub fn rb_iseq_reset_jit_func(iseq: *const rb_iseq_t); + pub fn rb_jit_get_page_size() -> u32; + pub fn rb_jit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_jit_for_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); + pub fn rb_jit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; + pub fn rb_jit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); + pub fn rb_jit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; + pub fn rb_jit_icache_invalidate( + start: *mut ::std::os::raw::c_void, + end: *mut ::std::os::raw::c_void, + ); } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index da97829e43629b..0c657f450a25dc 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -60,7 +60,7 @@ impl ZJITState { use crate::options::*; let exec_mem_bytes: usize = get_option!(exec_mem_bytes); - let virt_block: *mut u8 = unsafe { rb_zjit_reserve_addr_space(64 * 1024 * 1024) }; + let virt_block: *mut u8 = unsafe { rb_jit_reserve_addr_space(64 * 1024 * 1024) }; // Memory protection syscalls need page-aligned addresses, so check it here. Assuming // `virt_block` is page-aligned, `second_half` should be page-aligned as long as the @@ -69,7 +69,7 @@ impl ZJITState { // // Basically, we don't support x86-64 2MiB and 1GiB pages. ARMv8 can do up to 64KiB // (2¹ⶠbytes) pages, which should be fine. 4KiB pages seem to be the most popular though. - let page_size = unsafe { rb_zjit_get_page_size() }; + let page_size = unsafe { rb_jit_get_page_size() }; assert_eq!( virt_block as usize % page_size as usize, 0, "Start of virtual address block should be page-aligned", diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 11de4e08afe962..0d19858c8711f5 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -290,15 +290,15 @@ pub mod sys { impl super::Allocator for SystemAllocator { fn mark_writable(&mut self, ptr: *const u8, size: u32) -> bool { - unsafe { rb_zjit_mark_writable(ptr as VoidPtr, size) } + unsafe { rb_jit_mark_writable(ptr as VoidPtr, size) } } fn mark_executable(&mut self, ptr: *const u8, size: u32) { - unsafe { rb_zjit_mark_executable(ptr as VoidPtr, size) } + unsafe { rb_jit_mark_executable(ptr as VoidPtr, size) } } fn mark_unused(&mut self, ptr: *const u8, size: u32) -> bool { - unsafe { rb_zjit_mark_unused(ptr as VoidPtr, size) } + unsafe { rb_jit_mark_unused(ptr as VoidPtr, size) } } } } From ea8d49376375bf3029abe8851ef88f880eeb0e1a Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 12 Sep 2025 22:49:35 +0200 Subject: [PATCH 051/104] Explicitly use a ruby version for prism to parse the code as (#14523) Prism can parse multiple versions of ruby. Because of that branch release managers are ok with simply bumping prism to its latest version. However, if no version is specified, it will parse as the latest known version, which can be ahead of the maintenance branch. So we need to explicitly pass a version to not accidentally introduce new syntax to maintenance branches. --- depend | 2 ++ prism_compile.c | 12 ++++++++++++ prism_compile.h | 1 + 3 files changed, 15 insertions(+) diff --git a/depend b/depend index b9d91faa2a05a2..dbc002d5861080 100644 --- a/depend +++ b/depend @@ -1415,6 +1415,7 @@ compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h compile.$(OBJEXT): $(top_srcdir)/prism/version.h compile.$(OBJEXT): $(top_srcdir)/prism_compile.c +compile.$(OBJEXT): $(top_srcdir)/version.h compile.$(OBJEXT): {$(VPATH)}assert.h compile.$(OBJEXT): {$(VPATH)}atomic.h compile.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -1605,6 +1606,7 @@ compile.$(OBJEXT): {$(VPATH)}prism_compile.h compile.$(OBJEXT): {$(VPATH)}ractor.h compile.$(OBJEXT): {$(VPATH)}re.h compile.$(OBJEXT): {$(VPATH)}regex.h +compile.$(OBJEXT): {$(VPATH)}revision.h compile.$(OBJEXT): {$(VPATH)}ruby_assert.h compile.$(OBJEXT): {$(VPATH)}ruby_atomic.h compile.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/prism_compile.c b/prism_compile.c index 37909e49e01444..70081f3d95ad1f 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1,4 +1,5 @@ #include "prism.h" +#include "version.h" /** * This compiler defines its own concept of the location of a node. We do this @@ -11352,6 +11353,8 @@ pm_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines) pm_options_filepath_set(&result->options, RSTRING_PTR(filepath)); RB_GC_GUARD(filepath); + pm_options_version_for_current_ruby_set(&result->options); + pm_parser_init(&result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); pm_node_t *node = pm_parse(&result->parser); @@ -11410,6 +11413,8 @@ pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath, VALUE * pm_options_filepath_set(&result->options, RSTRING_PTR(filepath)); RB_GC_GUARD(filepath); + pm_options_version_for_current_ruby_set(&result->options); + pm_parser_init(&result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); pm_node_t *node = pm_parse(&result->parser); @@ -11492,6 +11497,13 @@ pm_parse_stdin(pm_parse_result_t *result) return pm_parse_process(result, node, NULL); } +#define PM_VERSION_FOR_RELEASE(major, minor) PM_VERSION_FOR_RELEASE_IMPL(major, minor) +#define PM_VERSION_FOR_RELEASE_IMPL(major, minor) PM_OPTIONS_VERSION_CRUBY_##major##_##minor + +void pm_options_version_for_current_ruby_set(pm_options_t *options) { + options->version = PM_VERSION_FOR_RELEASE(RUBY_VERSION_MAJOR, RUBY_VERSION_MINOR); +} + #undef NEW_ISEQ #define NEW_ISEQ OLD_ISEQ diff --git a/prism_compile.h b/prism_compile.h index c032449bd65ca9..e588122205948b 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -94,6 +94,7 @@ VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lin VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); VALUE pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath, VALUE *script_lines); VALUE pm_parse_stdin(pm_parse_result_t *result); +void pm_options_version_for_current_ruby_set(pm_options_t *options); void pm_parse_result_free(pm_parse_result_t *result); rb_iseq_t *pm_iseq_new(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent, enum rb_iseq_type, int *error_state); From 599f58fb8387b0cbb29c79579528bd6f444edea9 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 12 Sep 2025 16:47:54 -0500 Subject: [PATCH 052/104] [ruby/erb] [DOC] Enhanced documentation for class ERB (https://github.com/ruby/erb/pull/67) https://github.com/ruby/erb/commit/7646ece279 --- lib/erb.rb | 606 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 433 insertions(+), 173 deletions(-) diff --git a/lib/erb.rb b/lib/erb.rb index ebf91e4792d67e..c8aae4fd15239c 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -17,246 +17,506 @@ require 'erb/def_method' require 'erb/util' +# :markup: markdown # -# = ERB -- Ruby Templating +# Class **ERB** (the name stands for **Embedded Ruby**) +# is an easy-to-use, but also very powerful, [template processor][template processor]. # -# == Introduction +# Like method [sprintf][sprintf], \ERB can format run-time data into a string. +# \ERB, however,s is *much more powerful*. # -# ERB provides an easy to use but powerful templating system for Ruby. Using -# ERB, actual Ruby code can be added to any plain text document for the -# purposes of generating document information details and/or flow control. +# In simplest terms: # -# A very simple example is this: +# - You can create an \ERB object to store a text *template* that includes specially formatted *tags*; +# each tag specifies data that at run-time is to replace the tag in the produced result. +# - You can call instance method ERB#result to get the result, +# which is the string formed by replacing each tag with run-time data. # -# require 'erb' +# \ERB is commonly used to produce: # -# x = 42 -# template = ERB.new <<-EOF -# The value of x is: <%= x %> -# EOF -# puts template.result(binding) +# - Customized or personalized email messages. +# - Customized or personalized web pages. +# - Software code (in code-generating applications). # -# Prints: The value of x is: 42 +# ## Usage # -# More complex examples are given below. +# Before you can use \ERB, you must first require it +# (examples on this page assume that this has been done): # +# ``` +# require 'erb' +# ``` # -# == Recognized Tags +# ## In Brief # -# ERB recognizes certain tags in the provided template and converts them based -# on the rules below: +# ``` +# # Expression tag: begins with '<%', ends with '%>'. +# # This expression does not need the local binding. +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday." +# # This expression tag does need the local binding. +# magic_word = 'xyzzy' +# template.result(binding) # => "The magic word is xyzzy." # -# <% Ruby code -- inline with output %> -# <%= Ruby expression -- replace with result %> -# <%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.) -# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) -# %% replaced with % if first thing on a line and % processing is used -# <%% or %%> -- replace with <% or %> respectively +# Execution tag: begins with '<%=', ends with '%>'. +# s = '<% File.write("t.txt", "Some stuff.") %>' +# ERB.new(s).result +# File.read('t.txt') # => "Some stuff." # -# All other text is passed through ERB filtering unchanged. +# # Comment tag: begins with '<%#', ends with '%>'. +# s = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(s).result # => "Some stuff; more stuff." +# ``` # # -# == Options +# ## Some Simple Examples # -# There are several settings you can change when you use ERB: -# * the nature of the tags that are recognized; -# * the binding used to resolve local variables in the template. +# Here's a simple example of \ERB in action: # -# See the ERB.new and ERB#result methods for more detail. +# ``` +# s = 'The time is <%= Time.now %>.' +# template = ERB.new(s) +# template.result +# # => "The time is 2025-09-09 10:49:26 -0500." +# ``` # -# == Character encodings +# Details: # -# ERB (or Ruby code generated by ERB) returns a string in the same -# character encoding as the input string. When the input string has -# a magic comment, however, it returns a string in the encoding specified -# by the magic comment. +# 1. A plain-text string is assigned to variable `s`. +# Its embedded [expression tag][expression tags] `'<%= Time.now %>'` includes a Ruby expression, `Time.now`. +# 2. The string is put into a new \ERB object, and stored in variable `template`. +# 4. Method call `template.result` generates a string that contains the run-time value of `Time.now`, +# as computed at the time of the call. # -# # -*- coding: utf-8 -*- -# require 'erb' +# The template may be re-used: # -# template = ERB.new < -# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>. -# EOF -# puts template.result +# ``` +# template.result +# # => "The time is 2025-09-09 10:49:33 -0500." +# ``` # -# Prints: \_\_ENCODING\_\_ is Big5. +# Another example: # +# ``` +# s = 'The magic word is <%= magic_word %>.' +# template = ERB.new(s) +# magic_word = 'abracadabra' +# # => "abracadabra" +# template.result(binding) +# # => "The magic word is abracadabra." +# ``` # -# == Examples +# Details: # -# === Plain Text +# 1. As before, a plain-text string is assigned to variable `s`. +# Its embedded [expression tag][expression tags] `'<%= magic_word %>'` has a variable *name*, `magic_word`. +# 2. The string is put into a new \ERB object, and stored in variable `template`; +# note that `magic_word` need not be defined before the \ERB object is created. +# 3. `magic_word = 'abracadabra'` assigns a value to variable `magic_word`. +# 4. Method call `template.result(binding)` generates a string +# that contains the *value* of `magic_word`. # -# ERB is useful for any generic templating situation. Note that in this example, we use the -# convenient "% at start of line" tag, and we quote the template literally with -# %q{...} to avoid trouble with the backslash. +# As before, the template may be re-used: # -# require "erb" +# ``` +# magic_word = 'xyzzy' +# template.result(binding) +# # => "The magic word is xyzzy." +# ``` +# +# ## Bindings +# +# The first example above passed no argument to method `result`; +# the second example passed argument `binding`. +# +# Here's why: +# +# - The first example has tag `<%= Time.now %>`, +# which cites *globally-defined* constant `Time`; +# the default `binding` (details not needed here) includes the binding of global constant `Time` to its value. +# - The second example has tag `<%= magic_word %>`, +# which cites *locally-defined* variable `magic_word`; +# the passed argument `binding` (which is simply a call to method [Kernel#binding][kernel#binding]) +# includes the binding of local variable `magic_word` to its value. +# +# ## Tags +# +# The examples above use expression tags. +# These are the tags available in \ERB: +# +# - [Expression tag][expression tags]: the tag contains a Ruby exprssion; +# in the result, the entire tag is to be replaced with the run-time value of the expression. +# - [Execution tag][execution tags]: the tag contains Ruby code; +# in the result, the entire tag is to be replaced with the run-time value of the code. +# - [Comment tag][comment tags]: the tag contains comment code; +# in the result, the entire tag is to be omitted. +# +# ### Expression Tags +# +# You can embed a Ruby expression in a template using an *expression tag*. +# +# Its syntax is `<%= _expression_ %>`, +# where *expression* is any valid Ruby expression. +# +# When you call method #result, +# the method evaluates the expression and replaces the entire expression tag with the expression's value: +# +# ``` +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result +# # => "Today is Monday." +# ERB.new('Tomorrow will be <%= Date::DAYNAMES[Date.today.wday + 1] %>.').result +# # => "Tomorrow will be Tuesday." +# ERB.new('Yesterday was <%= Date::DAYNAMES[Date.today.wday - 1] %>.').result +# # => "Yesterday was Sunday." +# ``` +# +# Note that whitespace before and after the expression +# is allowed but not required, +# and that such whitespace is stripped from the result. +# +# ``` +# ERB.new('My appointment is on <%=Date::DAYNAMES[Date.today.wday + 2]%>.').result +# # => "My appointment is on Wednesday." +# ERB.new('My appointment is on <%= Date::DAYNAMES[Date.today.wday + 2] %>.').result +# # => "My appointment is on Wednesday." +# ``` +# +# ### Execution Tags +# +# You can embed Ruby executable code in template using an *execution tag*. +# +# Its syntax is `<% _code_ %>`, +# where *code* is any valid Ruby code. +# +# When you call method #result, +# the method executes the code and removes the entire execution tag +# (generating no text in the result). +# +# You can interleave text with execution tags to form a control structure +# such as a conditional, a loop, or a `case` statements. +# +# Conditional: +# +# ``` +# s = < +# An error has occurred. +# <% else %> +# Oops! +# <% end %> +# EOT +# template = ERB.new(s) +# verbosity = true +# template.result(binding) +# # => "\nAn error has occurred.\n\n" +# verbosity = false +# template.result(binding) +# # => "\nOops!\n\n" +# ``` +# +# Note that the interleaved text may itself contain expression tags: +# +# Loop: +# +# ``` +# s = < +# <%= dayname %> +# <% end %> +# EOT +# ERB.new(s).result +# # => "\nSun\n\nMon\n\nTue\n\nWed\n\nThu\n\nFri\n\nSat\n\n" +# ``` +# +# Other, non-control, lines of Ruby code may be interleaved with the text, +# and the Ruby code may itself contain regular Ruby comments: +# +# ``` +# s = < +# <%= Time.now %> +# <% sleep(1) # Let's make the times different. %> +# <% end %> +# EOT +# ERB.new(s).result +# # => "\n2025-09-09 11:36:02 -0500\n\n\n2025-09-09 11:36:03 -0500\n\n\n2025-09-09 11:36:04 -0500\n\n\n" +# ``` +# +# The execution tag may also contain multiple lines of code: +# +# ``` +# s = < +# * <%=i%>,<%=j%> +# <% +# end +# end +# %> +# EOT +# ERB.new(s).result +# # => "\n* 0,0\n\n* 0,1\n\n* 0,2\n\n* 1,0\n\n* 1,1\n\n* 1,2\n\n* 2,0\n\n* 2,1\n\n* 2,2\n\n" +# ``` +# +# You can use keyword argument `trim_mode` to make certain adjustments to the processing; +# see ERB.new. +# +# In particular, you can give `trim_mode: '%'` to enable a shorthand format for execution tags; +# this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: +# +# ``` +# s = < +# % end +# EOT +# template = ERB.new(s, trim_mode: '%') +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# puts template.result(binding) +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk +# ``` +# +# Note that in the shorthand format, the character `'%'` must be the first character in the code line +# (no leading whitespace). # -# # Create template. -# template = %q{ -# From: James Edward Gray II -# To: <%= to %> -# Subject: Addressing Needs +# ### Comment Tags # -# <%= to[/\w+/] %>: +# You can embed a comment in a template using a *comment tag*; +# its syntax is `<%# _text_ %>`, +# where *text* is the text of the comment. # -# Just wanted to send a quick note assuring that your needs are being -# addressed. +# When you call method #result, +# it removes the entire comment tag +# (generating no text in the result). # -# I want you to know that my team will keep working on the issues, -# especially: +# Example: # -# <%# ignore numerous minor requests -- focus on priorities %> -# % priorities.each do |priority| -# * <%= priority %> -# % end +# ``` +# s = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(s).result # => "Some stuff; more stuff." +# ``` # -# Thanks for your patience. +# A comment tag may appear anywhere in the template text. # -# James Edward Gray II -# }.gsub(/^ /, '') +# Note that the beginning of the tag must be `'<%#'`, not `'<% #'`. # -# message = ERB.new(template, trim_mode: "%<>") +# In this example, the tag begins with `'<% #'`, and so is an execution tag, not a comment tag; +# the cited code consists entirely of a Ruby-style comment (which is of course ignored): # -# # Set up template data. -# to = "Community Spokesman " -# priorities = [ "Run Ruby Quiz", -# "Document Modules", -# "Answer Questions on Ruby Talk" ] +# ``` +# ERB.new('Some stuff;<% # Note to self: figure out what the stuff is. %> more stuff.').result +# # => "Some stuff;" +# ``` # -# # Produce result. -# email = message.result -# puts email +# ## Encodings # -# Generates: +# In general, an \ERB result string (or Ruby code generated by \ERB) +# has the same encoding as the string originally passed to ERB.new; +# see [Encoding][encoding]. # -# From: James Edward Gray II -# To: Community Spokesman -# Subject: Addressing Needs +# You can specify the output encoding by adding a [magic comment][magic comments] +# at the top of the given string: # -# Community: +# ``` +# s = < # -# Just wanted to send a quick note assuring that your needs are being addressed. +# Some text. +# EOF +# # => "<%#-*- coding: Big5 -*-%>\n\nSome text.\n" +# s.encoding +# # => # +# ERB.new(s).result.encoding +# # => # +# ``` # -# I want you to know that my team will keep working on the issues, especially: +# ## Plain Text Example # -# * Run Ruby Quiz -# * Document Modules -# * Answer Questions on Ruby Talk +# Here's a plain-text string; +# it uses the literal notation `'%q{ ... }'` to define the string +# (see [%q literals][%q literals]); +# this avoids problems with backslashes. # -# Thanks for your patience. +# ``` +# s = %q{ +# From: James Edward Gray II +# To: <%= to %> +# Subject: Addressing Needs # -# James Edward Gray II +# <%= to[/\w+/] %>: # -# === Ruby in HTML +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# ERB is often used in .rhtml files (HTML with embedded Ruby). Notice the need in -# this example to provide a special binding when the template is run, so that the instance -# variables in the Product object can be resolved. +# I want you to know that my team will keep working on the issues, +# especially: # -# require "erb" +# <%# ignore numerous minor requests -- focus on priorities %> +# % priorities.each do |priority| +# * <%= priority %> +# % end # -# # Build template data class. -# class Product -# def initialize( code, name, desc, cost ) -# @code = code -# @name = name -# @desc = desc -# @cost = cost +# Thanks for your patience. # -# @features = [ ] -# end +# James Edward Gray II +# } +# ``` # -# def add_feature( feature ) -# @features << feature -# end +# The template will need these: # -# # Support templating of member data. -# def get_binding -# binding -# end +# ``` +# to = 'Community Spokesman ' +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# ``` # -# # ... -# end +# Finally, make the template and get the result # -# # Create template. -# template = %{ -# -# Ruby Toys -- <%= @name %> -# +# ``` +# template = ERB.new(s, trim_mode: '%<>') +# puts template.result(binding) # -#

<%= @name %> (<%= @code %>)

-#

<%= @desc %>

+# From: James Edward Gray II +# To: Community Spokesman +# Subject: Addressing Needs # -#
    -# <% @features.each do |f| %> -#
  • <%= f %>
  • -# <% end %> -#
+# Community: # -#

-# <% if @cost < 10 %> -# Only <%= @cost %>!!! -# <% else %> -# Call for a price, today! -# <% end %> -#

+# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# -# -# }.gsub(/^ /, '') +# I want you to know that my team will keep working on the issues, +# especially: # -# rhtml = ERB.new(template) +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk # -# # Set up template data. -# toy = Product.new( "TZ-1002", -# "Rubysapien", -# "Geek's Best Friend! Responds to Ruby commands...", -# 999.95 ) -# toy.add_feature("Listens for verbal commands in the Ruby language!") -# toy.add_feature("Ignores Perl, Java, and all C variants.") -# toy.add_feature("Karate-Chop Action!!!") -# toy.add_feature("Matz signature on left leg.") -# toy.add_feature("Gem studded eyes... Rubies, of course!") +# Thanks for your patience. # -# # Produce result. -# rhtml.run(toy.get_binding) +# James Edward Gray II +# ``` # -# Generates (some blank lines removed): +# ## HTML Example # -# -# Ruby Toys -- Rubysapien -# +# This example shows an HTML template. # -#

Rubysapien (TZ-1002)

-#

Geek's Best Friend! Responds to Ruby commands...

-# -#
    -#
  • Listens for verbal commands in the Ruby language!
  • -#
  • Ignores Perl, Java, and all C variants.
  • -#
  • Karate-Chop Action!!!
  • -#
  • Matz signature on left leg.
  • -#
  • Gem studded eyes... Rubies, of course!
  • -#
-# -#

-# Call for a price, today! -#

-# -# -# +# First, here's a custom class, `Product`: # +# ``` +# class Product +# def initialize(code, name, desc, cost) +# @code = code +# @name = name +# @desc = desc +# @cost = cost +# @features = [] +# end # -# == Notes +# def add_feature(feature) +# @features << feature +# end # -# There are a variety of templating solutions available in various Ruby projects. -# For example, RDoc, distributed with Ruby, uses its own template engine, which -# can be reused elsewhere. +# # Support templating of member data. +# def get_binding +# binding +# end # -# Other popular engines could be found in the corresponding -# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of -# The Ruby Toolbox. +# end +# ``` +# +# The template below will need these values: +# +# ``` +# toy = Product.new('TZ-1002', +# 'Rubysapien', +# "Geek's Best Friend! Responds to Ruby commands...", +# 999.95 +# ) +# toy.add_feature('Listens for verbal commands in the Ruby language!') +# toy.add_feature('Ignores Perl, Java, and all C variants.') +# toy.add_feature('Karate-Chop Action!!!') +# toy.add_feature('Matz signature on left leg.') +# toy.add_feature('Gem studded eyes... Rubies, of course!') +# ``` +# +# Here's the HTML: +# +# ``` +# s = < +# Ruby Toys -- <%= @name %> +# +#

<%= @name %> (<%= @code %>)

+#

<%= @desc %>

+#
    +# <% @features.each do |f| %> +#
  • <%= f %>
  • +# <% end %> +#
+#

+# <% if @cost < 10 %> +# Only <%= @cost %>!!! +# <% else %> +# Call for a price, today! +# <% end %> +#

+# +# +# EOT +# ``` +# +# Finally, build the template and get the result (omitting some blank lines): +# +# ``` +# template = ERB.new(s) +# puts template.result(toy.get_binding) +# +# Ruby Toys -- Rubysapien +# +#

Rubysapien (TZ-1002)

+#

Geek's Best Friend! Responds to Ruby commands...

+#
    +#
  • Listens for verbal commands in the Ruby language!
  • +#
  • Ignores Perl, Java, and all C variants.
  • +#
  • Karate-Chop Action!!!
  • +#
  • Matz signature on left leg.
  • +#
  • Gem studded eyes... Rubies, of course!
  • +#
+#

+# Call for a price, today! +#

+# +# +# ``` +# +# +# ## Other Template Processors +# +# Various Ruby projects have their own template processors. +# The Ruby Processing System [RDoc][rdoc], for example, has one that can be used elsewhere. +# +# Other popular template processors may found in the [Template Engines][template engines] page +# of the Ruby Toolbox. +# +# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html +# [comment tags]: rdoc-ref:ERB@Comment+Tags +# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html +# [execution tags]: rdoc-ref:ERB@Execution+Tags +# [expression tags]: rdoc-ref:ERB@Expression+Tags +# [kernel#binding]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-binding +# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals +# [magic comments]: https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html#label-Magic+Comments +# [rdoc]: https://ruby.github.io/rdoc +# [sprintf]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sprintf +# [template engines]: https://www.ruby-toolbox.com/categories/template_engines +# [template processor]: https://en.wikipedia.org/wiki/Template_processor # class ERB Revision = '$Date:: $' # :nodoc: #' @@ -286,7 +546,7 @@ def self.version # templates through the same binding and/or when you want to control where # output ends up. Pass the name of the variable to be used inside a String. # - # === Example + # ### Example # # require "erb" # From a8c829e7f0dc20c3213d23fb9722fabc74fe1a9d Mon Sep 17 00:00:00 2001 From: yui-knk Date: Fri, 29 Aug 2025 16:26:52 +0900 Subject: [PATCH 053/104] [Bug #17398] Allow `private def hello = puts "Hello"` --- parse.y | 5 +++++ test/.excludes-parsey/TestSyntax.rb | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) delete mode 100644 test/.excludes-parsey/TestSyntax.rb diff --git a/parse.y b/parse.y index a6c5e2e5b94f3f..d2419b0bac2aaa 100644 --- a/parse.y +++ b/parse.y @@ -4198,6 +4198,11 @@ call_args : value_expr(command) $$ = NEW_LIST($1, &@$); /*% ripper: args_add!(args_new!, $:1) %*/ } + | def_endless_method(endless_command) + { + $$ = NEW_LIST($1, &@$); + /*% ripper: args_add!(args_new!, $:1) %*/ + } | args opt_block_arg { $$ = arg_blk_pass($1, $2); diff --git a/test/.excludes-parsey/TestSyntax.rb b/test/.excludes-parsey/TestSyntax.rb deleted file mode 100644 index ff7307060dd2a4..00000000000000 --- a/test/.excludes-parsey/TestSyntax.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_methoddef_endless_command, "[Bug #17398]") From 10f748480be666a280a20826a73824129e91cd88 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:14:59 +0900 Subject: [PATCH 054/104] Just touch the timestamp for prism/srcs.mk when no baseruby --- prism/srcs.mk | 3 +++ prism/srcs.mk.in | 3 +++ 2 files changed, 6 insertions(+) diff --git a/prism/srcs.mk b/prism/srcs.mk index aa5c0fa2b5ee33..565d793cc0a210 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -5,6 +5,9 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs uncommon.mk: prism/.srcs.mk.time prism/.srcs.mk.time: +prism/$(HAVE_BASERUBY:no=.srcs.mk.time): + mkdir -p $(@D) + touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ $(PRISM_SRCDIR)/srcs.mk.in diff --git a/prism/srcs.mk.in b/prism/srcs.mk.in index 655de155d5e97b..0b921b7f0bb07d 100644 --- a/prism/srcs.mk.in +++ b/prism/srcs.mk.in @@ -13,6 +13,9 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs <%=%><%=mk%>: prism/.srcs.mk.time prism/.srcs.mk.time: +prism/$(HAVE_BASERUBY:no=.srcs.mk.time): + mkdir -p $(@D) + touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ $(PRISM_SRCDIR)/<%=%><%=script%> From 618224adf620bd72645b3fada4f51e805196f5ca Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 23 Dec 2024 14:39:07 +0900 Subject: [PATCH 055/104] [Feature #20925] Support leading logical operators --- parse.y | 28 ++++++++++++++++++++++ test/ruby/test_syntax.rb | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/parse.y b/parse.y index d2419b0bac2aaa..9e81baa68bfd0b 100644 --- a/parse.y +++ b/parse.y @@ -6985,6 +6985,16 @@ is_identchar(struct parser_params *p, const char *ptr, const char *MAYBE_UNUSED( return rb_enc_isalnum((unsigned char)*ptr, enc) || *ptr == '_' || !ISASCII(*ptr); } +static inline bool +peek_word_at(struct parser_params *p, const char *str, size_t len, int at) +{ + const char *ptr = p->lex.pcur + at; + if (lex_eol_ptr_n_p(p, ptr, len-1)) return false; + if (memcmp(ptr, str, len)) return false; + if (lex_eol_ptr_n_p(p, ptr, len)) return true; + return !is_identchar(p, ptr+len, p->lex.pend, p->enc); +} + static inline int parser_is_identchar(struct parser_params *p) { @@ -10556,7 +10566,24 @@ parser_yylex(struct parser_params *p) token_flush(p); } goto retry; + case 'a': + if (peek_word_at(p, "nd", 2, 0)) goto leading_logical; + goto bol; + case 'o': + if (peek_word_at(p, "r", 1, 0)) goto leading_logical; + goto bol; + case '|': + if (peek(p, '|')) goto leading_logical; + goto bol; case '&': + if (peek(p, '&')) { + leading_logical: + pushback(p, c); + dispatch_delayed_token(p, tIGNORED_NL); + cmd_state = FALSE; + goto retry; + } + /* fall through */ case '.': { dispatch_delayed_token(p, tIGNORED_NL); if (peek(p, '.') == (c == '&')) { @@ -10565,6 +10592,7 @@ parser_yylex(struct parser_params *p) goto retry; } } + bol: default: p->ruby_sourceline--; p->lex.nextline = p->lex.lastline; diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 7e2185be39d47c..19cce29075d49b 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1259,6 +1259,56 @@ def test_fluent_dot assert_valid_syntax("a #\n#\n&.foo\n") end + def test_fluent_and + omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION + + assert_valid_syntax("a\n" "&& foo") + assert_valid_syntax("a\n" "and foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + && (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + and (a = :ok; true) + a + end + end; + end + + def test_fluent_or + omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION + + assert_valid_syntax("a\n" "|| foo") + assert_valid_syntax("a\n" "or foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + || (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + or (a = :ok; true) + a + end + end; + end + def test_safe_call_in_massign_lhs assert_syntax_error("*a&.x=0", /multiple assignment destination/) assert_syntax_error("a&.x,=0", /multiple assignment destination/) From ff93185f4a2fd6048023a1c8a676b25fe474ef11 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 14:50:57 +0900 Subject: [PATCH 056/104] Remove excludes --- test/.excludes-parsey/Prism/FixturesTest.rb | 1 - test/.excludes-parsey/Prism/LocalsTest.rb | 1 - 2 files changed, 2 deletions(-) delete mode 100644 test/.excludes-parsey/Prism/FixturesTest.rb delete mode 100644 test/.excludes-parsey/Prism/LocalsTest.rb diff --git a/test/.excludes-parsey/Prism/FixturesTest.rb b/test/.excludes-parsey/Prism/FixturesTest.rb deleted file mode 100644 index 452ff4f5058746..00000000000000 --- a/test/.excludes-parsey/Prism/FixturesTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:"test_leading_logical.txt", "Requires Feature #20925 to be implemented on parse.y") diff --git a/test/.excludes-parsey/Prism/LocalsTest.rb b/test/.excludes-parsey/Prism/LocalsTest.rb deleted file mode 100644 index 452ff4f5058746..00000000000000 --- a/test/.excludes-parsey/Prism/LocalsTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:"test_leading_logical.txt", "Requires Feature #20925 to be implemented on parse.y") From 91e56471151ea8250fdd9d3d1fd0ccf9e5416ae3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 19 Aug 2025 23:54:51 +0900 Subject: [PATCH 057/104] Get rid of `strcpy` On OpenBSD: ``` ld: warning: namespace.c:731(namespace.o:(rb_namespace_local_extension)): warning: strcpy() is almost always misused, please use strlcpy() ``` --- addr2line.c | 5 +++-- ext/socket/getaddrinfo.c | 4 +--- ext/socket/getnameinfo.c | 15 +++++---------- ext/socket/raddrinfo.c | 4 ++-- namespace.c | 27 +++++++++++++++------------ 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/addr2line.c b/addr2line.c index 745364cc0f78e8..19a6a425c1e138 100644 --- a/addr2line.c +++ b/addr2line.c @@ -637,12 +637,13 @@ follow_debuglink_build_id(const char *build_id, size_t build_id_size, int num_tr obj_info_t **objp, line_info_t *lines, int offset, FILE *errout) { static const char global_debug_dir[] = "/usr/lib/debug/.build-id/"; + static const char debug_suffix[] = ".debug"; const size_t global_debug_dir_len = sizeof(global_debug_dir) - 1; char *p; obj_info_t *o1 = *objp, *o2; size_t i; - if (PATH_MAX < global_debug_dir_len + 1 + build_id_size * 2 + 6) return; + if (PATH_MAX < global_debug_dir_len + build_id_size * 2 + sizeof(debug_suffix)) return; memcpy(binary_filename, global_debug_dir, global_debug_dir_len); p = binary_filename + global_debug_dir_len; @@ -653,7 +654,7 @@ follow_debuglink_build_id(const char *build_id, size_t build_id_size, int num_tr *p++ = tbl[n % 16]; if (i == 0) *p++ = '/'; } - strcpy(p, ".debug"); + memcpy(p, debug_suffix, sizeof(debug_suffix)); append_obj(objp); o2 = *objp; diff --git a/ext/socket/getaddrinfo.c b/ext/socket/getaddrinfo.c index bf0d90129f7601..5b824996552e5f 100644 --- a/ext/socket/getaddrinfo.c +++ b/ext/socket/getaddrinfo.c @@ -171,9 +171,7 @@ static const char *const ai_errlist[] = { #define GET_CANONNAME(ai, str) \ if (pai->ai_flags & AI_CANONNAME) {\ - if (((ai)->ai_canonname = (char *)malloc(strlen(str) + 1)) != NULL) {\ - strcpy((ai)->ai_canonname, (str));\ - } else {\ + if (((ai)->ai_canonname = strdup(str)) == NULL) {\ error = EAI_MEMORY;\ goto free;\ }\ diff --git a/ext/socket/getnameinfo.c b/ext/socket/getnameinfo.c index ae5284fab6fc59..ffda7f98c73f6d 100644 --- a/ext/socket/getnameinfo.c +++ b/ext/socket/getnameinfo.c @@ -158,16 +158,14 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho /* what we should do? */ } else if (flags & NI_NUMERICSERV) { snprintf(numserv, sizeof(numserv), "%d", ntohs(port)); - if (strlen(numserv) + 1 > servlen) + if (strlcpy(serv, numserv, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, numserv); } else { #if defined(HAVE_GETSERVBYPORT) struct servent *sp = getservbyport(port, (flags & NI_DGRAM) ? "udp" : "tcp"); if (sp) { - if (strlen(sp->s_name) + 1 > servlen) + if (strlcpy(serv, sp->s_name, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, sp->s_name); } else return ENI_NOSERVNAME; #else @@ -202,9 +200,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_SYSTEM; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } else { #ifdef INET6 hp = getipnodebyaddr(addr, afd->a_addrlen, afd->a_af, &h_error); @@ -218,13 +215,12 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho p = strchr(hp->h_name, '.'); if (p) *p = '\0'; } - if (strlen(hp->h_name) + 1 > hostlen) { + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) { #ifdef INET6 freehostent(hp); #endif return ENI_MEMORY; } - strcpy(host, hp->h_name); #ifdef INET6 freehostent(hp); #endif @@ -234,9 +230,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_NOHOSTNAME; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } } return SUCCESS; diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 6bacc1c22162a2..22ab34a073a0f1 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -387,7 +387,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (hostp) { arg->node = buf + hostp_offset; - strcpy(arg->node, hostp); + memcpy(arg->node, hostp, portp_offset - hostp_offset); } else { arg->node = NULL; @@ -395,7 +395,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (portp) { arg->service = buf + portp_offset; - strcpy(arg->service, portp); + memcpy(arg->service, portp, bufsize - portp_offset); } else { arg->service = NULL; diff --git a/namespace.c b/namespace.c index 55b7580c72e3ab..0e1c5550048527 100644 --- a/namespace.c +++ b/namespace.c @@ -725,28 +725,31 @@ copy_ext_file(char *src_path, char *dst_path) #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) static void -fname_without_suffix(char *fname, char *rvalue) +fname_without_suffix(const char *fname, char *rvalue, size_t rsize) { - char *pos; - strcpy(rvalue, fname); - for (pos = rvalue + strlen(fname); pos > rvalue; pos--) { + size_t len = strlen(fname); + const char *pos; + for (pos = fname + len; pos > fname; pos--) { if (IS_SOEXT(pos) || IS_DLEXT(pos)) { - *pos = '\0'; - return; + len = pos - fname; + break; } } + if (len > rsize - 1) len = rsize - 1; + memcpy(rvalue, fname, len); + rvalue[len] = '\0'; } static void -escaped_basename(char *path, char *fname, char *rvalue) +escaped_basename(const char *path, const char *fname, char *rvalue, size_t rsize) { - char *pos, *leaf, *found; - leaf = path; + char *pos; + const char *leaf = path, *found; // `leaf + 1` looks uncomfortable (when leaf == path), but fname must not be the top-dir itself while ((found = strstr(leaf + 1, fname)) != NULL) { leaf = found; // find the last occurrence for the path like /etc/my-crazy-lib-dir/etc.so } - strcpy(rvalue, leaf); + strlcpy(rvalue, leaf, rsize); for (pos = rvalue; *pos; pos++) { if (isdirsep(*pos)) { *pos = '+'; @@ -762,8 +765,8 @@ rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path) char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); rb_namespace_t *ns = rb_get_namespace_t(namespace); - fname_without_suffix(fname_ptr, fname2); - escaped_basename(src_path, fname2, basename); + fname_without_suffix(fname_ptr, fname2, sizeof(fname2)); + escaped_basename(src_path, fname2, basename, sizeof(basename)); wrote = sprint_ext_filename(ext_path, sizeof(ext_path), ns->ns_id, NAMESPACE_TMP_PREFIX, basename); if (wrote >= (int)sizeof(ext_path)) { From 9b40d2b27514ac510ff9f8dc84e92e1796e5ffcc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:12:38 +0900 Subject: [PATCH 058/104] Stop at max dlext size --- namespace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/namespace.c b/namespace.c index 0e1c5550048527..28e2c63a81940e 100644 --- a/namespace.c +++ b/namespace.c @@ -734,6 +734,7 @@ fname_without_suffix(const char *fname, char *rvalue, size_t rsize) len = pos - fname; break; } + if (fname + len - pos > DLEXT_MAXLEN) break; } if (len > rsize - 1) len = rsize - 1; memcpy(rvalue, fname, len); From bb5cd8e0496f567d9e5c4435e9d24113d30212ba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 16:36:48 +0900 Subject: [PATCH 059/104] Get rid of `strcpy` and magic numbers --- gc/default/default.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index d5b18d20b96c8b..7264fb799627eb 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4392,15 +4392,16 @@ static inline void gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj) { if (RB_UNLIKELY(RB_TYPE_P(obj, T_NONE))) { - char obj_info_buf[256]; - rb_raw_obj_info(obj_info_buf, 256, obj); + enum {info_size = 256}; + char obj_info_buf[info_size]; + rb_raw_obj_info(obj_info_buf, info_size, obj); - char parent_obj_info_buf[256]; + char parent_obj_info_buf[info_size]; if (objspace->rgengc.parent_object == Qfalse) { - strcpy(parent_obj_info_buf, "(none)"); + strlcpy(parent_obj_info_buf, "(none)", info_size); } else { - rb_raw_obj_info(parent_obj_info_buf, 256, objspace->rgengc.parent_object); + rb_raw_obj_info(parent_obj_info_buf, info_size, objspace->rgengc.parent_object); } rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf); From 717ad9f41edc9a2349b6f665dbe49c02e6841370 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:16:59 +0900 Subject: [PATCH 060/104] Remove an unused expression --- ext/socket/ipsocket.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index edd270c3adafcd..fa41d89936fa2f 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -208,8 +208,8 @@ rsock_init_inetsock( VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, - VALUE _fast_fallback, VALUE _test_mode_settings -) { + VALUE _fast_fallback, VALUE _test_mode_settings) +{ if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); } @@ -423,8 +423,8 @@ select_expires_at( struct timeval *connection_attempt_delay, struct timeval *user_specified_resolv_timeout_at, struct timeval *user_specified_connect_timeout_at, - struct timeval *user_specified_open_timeout_at -) { + struct timeval *user_specified_open_timeout_at) +{ if (any_addrinfos(resolution_store)) { struct timeval *delay; delay = resolution_delay ? resolution_delay : connection_attempt_delay; @@ -526,7 +526,8 @@ in_progress_fds(int fds_size) } static void -remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) { +remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) +{ int i, j; for (i = 0; i < *fds_size; i++) { @@ -1283,8 +1284,8 @@ rsock_init_inetsock( VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, - VALUE fast_fallback, VALUE test_mode_settings -) { + VALUE fast_fallback, VALUE test_mode_settings) +{ if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); } @@ -1315,7 +1316,7 @@ rsock_init_inetsock( ); struct addrinfo *tmp_p = local_res->ai; - for (tmp_p; tmp_p != NULL; tmp_p = tmp_p->ai_next) { + for (; tmp_p != NULL; tmp_p = tmp_p->ai_next) { if (target_families[0] == 0 && tmp_p->ai_family == AF_INET6) { target_families[0] = AF_INET6; resolving_family_size++; From 234f4c0bb62b394971eb1044c9c0b1f994d1d4c4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:17:23 +0900 Subject: [PATCH 061/104] Fix dangling pointers --- prism/util/pm_string.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prism/util/pm_string.c b/prism/util/pm_string.c index 75422fbdf2c41d..cf79885fddbdff 100644 --- a/prism/util/pm_string.c +++ b/prism/util/pm_string.c @@ -182,7 +182,7 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { if (size == 0) { close(fd); - const uint8_t source[] = ""; + static const uint8_t source[] = ""; *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; return PM_STRING_INIT_SUCCESS; } @@ -278,7 +278,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) { size_t size = (size_t) sb.st_size; if (size == 0) { close(fd); - const uint8_t source[] = ""; + static const uint8_t source[] = ""; *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; return PM_STRING_INIT_SUCCESS; } From 9620964f9c1f2253b350339ee96decf0c04dbbbf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 18:02:55 +0900 Subject: [PATCH 062/104] Use API version for syntax version instead of program version --- depend | 2 -- prism_compile.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/depend b/depend index dbc002d5861080..b9d91faa2a05a2 100644 --- a/depend +++ b/depend @@ -1415,7 +1415,6 @@ compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h compile.$(OBJEXT): $(top_srcdir)/prism/version.h compile.$(OBJEXT): $(top_srcdir)/prism_compile.c -compile.$(OBJEXT): $(top_srcdir)/version.h compile.$(OBJEXT): {$(VPATH)}assert.h compile.$(OBJEXT): {$(VPATH)}atomic.h compile.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -1606,7 +1605,6 @@ compile.$(OBJEXT): {$(VPATH)}prism_compile.h compile.$(OBJEXT): {$(VPATH)}ractor.h compile.$(OBJEXT): {$(VPATH)}re.h compile.$(OBJEXT): {$(VPATH)}regex.h -compile.$(OBJEXT): {$(VPATH)}revision.h compile.$(OBJEXT): {$(VPATH)}ruby_assert.h compile.$(OBJEXT): {$(VPATH)}ruby_atomic.h compile.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/prism_compile.c b/prism_compile.c index 70081f3d95ad1f..74f10024cb29ac 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1,5 +1,5 @@ #include "prism.h" -#include "version.h" +#include "ruby/version.h" /** * This compiler defines its own concept of the location of a node. We do this @@ -11501,7 +11501,7 @@ pm_parse_stdin(pm_parse_result_t *result) #define PM_VERSION_FOR_RELEASE_IMPL(major, minor) PM_OPTIONS_VERSION_CRUBY_##major##_##minor void pm_options_version_for_current_ruby_set(pm_options_t *options) { - options->version = PM_VERSION_FOR_RELEASE(RUBY_VERSION_MAJOR, RUBY_VERSION_MINOR); + options->version = PM_VERSION_FOR_RELEASE(RUBY_API_VERSION_MAJOR, RUBY_API_VERSION_MINOR); } #undef NEW_ISEQ From caa5d8cdd7483647013af5e3d1701b0ed3ec8785 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 19:29:05 +0900 Subject: [PATCH 063/104] Exclude `JSON::GenericObject` test It depends on ostruct gem that is no longer a part of the default gems, and the all tests are just skipped with a warning. --- test/.excludes/JSONGenericObjectTest.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 test/.excludes/JSONGenericObjectTest.rb diff --git a/test/.excludes/JSONGenericObjectTest.rb b/test/.excludes/JSONGenericObjectTest.rb new file mode 100644 index 00000000000000..820a6a01200182 --- /dev/null +++ b/test/.excludes/JSONGenericObjectTest.rb @@ -0,0 +1,4 @@ +# ostruct will be loaded when JSON::GenericObject is autoloaded. By +# removing all test methods, the autoload in `setup` is not triggered. + +exclude /test_/, 'JSON::GenericObject needs ostruct gem' From 46095d26805de11571697b30adfe234db18efcc9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 15:03:56 +0900 Subject: [PATCH 064/104] auto-style.rb: Allow to adjust the given files --- tool/auto-style.rb | 144 +++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 65 deletions(-) diff --git a/tool/auto-style.rb b/tool/auto-style.rb index b673e3d1771042..921ffe11f922f6 100755 --- a/tool/auto-style.rb +++ b/tool/auto-style.rb @@ -11,9 +11,11 @@ class Git def initialize(oldrev, newrev, branch = nil) @oldrev = oldrev - @newrev = newrev.empty? ? 'HEAD' : newrev + @newrev = !newrev || newrev.empty? ? 'HEAD' : newrev @branch = branch + return unless oldrev + # GitHub may not fetch github.event.pull_request.base.sha at checkout git('log', '--format=%H', '-1', @oldrev, out: IO::NULL, err: [:child, :out]) or git('fetch', '--depth=1', 'origin', @oldrev) @@ -59,7 +61,7 @@ def commit(log, *files) end def push - git('push', 'origin', @branch) + git('push', 'origin', @branch) if @branch end def diff @@ -181,85 +183,97 @@ def with_clean_env addr2line.c io_buffer.c prism*.c scheduler.c ] -oldrev, newrev, pushref = ARGV -unless dry_run = pushref.empty? - branch = IO.popen(['git', 'rev-parse', '--symbolic', '--abbrev-ref', pushref], &:read).strip -end -git = Git.new(oldrev, newrev, branch) +def adjust_styles(files) + trailing = eofnewline = expandtab = indent = false -updated_files = git.updated_paths -files = updated_files.select {|l| - /^\d/ !~ l and /\.bat\z/ !~ l and - (/\A(?:config|[Mm]akefile|GNUmakefile|README)/ =~ File.basename(l) or - /\A\z|\.(?:[chsy]|\d+|e?rb|tmpl|bas[eh]|z?sh|in|ma?k|def|src|trans|rdoc|ja|en|el|sed|awk|p[ly]|scm|mspec|html|rs)\z/ =~ File.extname(l)) -} -files.select! {|n| File.file?(n) } -files.reject! do |f| - IGNORED_FILES.any? { |re| f.match(re) } -end -if files.empty? - puts "No files are an auto-style target:\n#{updated_files.join("\n")}" - exit -end + edited_files = files.select do |f| + src = File.binread(f) rescue next + eofnewline = eofnewline0 = true if src.sub!(/(? Date: Sat, 13 Sep 2025 15:04:20 +0900 Subject: [PATCH 065/104] auto-style.rb: Adjust .y file as same as .c --- tool/auto-style.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/auto-style.rb b/tool/auto-style.rb index 921ffe11f922f6..259ed377bc7036 100755 --- a/tool/auto-style.rb +++ b/tool/auto-style.rb @@ -217,7 +217,7 @@ def adjust_styles(files) end end - if File.fnmatch?("*.[ch]", f, File::FNM_PATHNAME) && + if File.fnmatch?("*.[chy]", f, File::FNM_PATHNAME) && !DIFFERENT_STYLE_FILES.any? {|pat| File.fnmatch?(pat, f, File::FNM_PATHNAME)} indent0 = true if src.gsub!(/^\w+\([^\n]*?\)\K[ \t]*(?=\{( *\\)?$)/, '\1' "\n") indent0 = true if src.gsub!(/^([ \t]*)\}\K[ \t]*(?=else\b.*?( *\\)?$)/, '\2' "\n" '\1') From d2cea4b68848bf6d8edb79b2da688b1d85e952a5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 20:10:42 +0900 Subject: [PATCH 066/104] * adjust indents. [ci skip] --- parse.y | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/parse.y b/parse.y index 9e81baa68bfd0b..6fac3b8ec79760 100644 --- a/parse.y +++ b/parse.y @@ -5897,7 +5897,8 @@ strings : string { if (!$1) { $$ = NEW_STR(STRING_NEW0(), &@$); - } else { + } + else { $$ = evstr2dstr(p, $1); } /*% ripper: $:1 %*/ @@ -6922,7 +6923,8 @@ parser_dispatch_delayed_token(struct parser_params *p, enum yytokentype t, int l if (p->keep_tokens) { /* p->delayed.token is freed by rb_parser_tokens_free */ parser_append_tokens(p, p->delayed.token, t, line); - } else { + } + else { rb_parser_string_free(p, p->delayed.token); } From ea8b346cbc03bae4857d32456bbd2895ae82e770 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 30 Aug 2025 13:03:15 +0900 Subject: [PATCH 067/104] Suppress warning from net/http.rb in ruby 3.1 When `-F` option is given to strip comments in bundled_gems file, `$;` is set to the regexp. On the other hand, old net/http.rb contained old CVS keyword expansion that is `split` with the default separator, and non-nil `$;` causes a warning. ```ruby Revision = %q$Revision$.split[1] ``` --- tool/downloader.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/downloader.rb b/tool/downloader.rb index 14f18747f3849e..39ebf44a83cbb0 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -6,7 +6,9 @@ require 'fileutils' require 'open-uri' require 'pathname' +verbose, $VERBOSE = $VERBOSE, nil require 'net/https' +$VERBOSE = verbose class Downloader def self.find(dlname) From aaa9c1913655acd80aea7e7fd2cf2a3fc1db2083 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 22:19:47 +0900 Subject: [PATCH 068/104] [DOC] Markup the autolink excluded word library name `Set` --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4679ef9a289380..1d96fc3ad2e1e0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -98,9 +98,9 @@ Note: We're only listing outstanding class updates. * `Ractor#close_incoming` and `Ractor#close_outgoing` were removed. -* Set +* `Set` - * Set is now a core class, instead of an autoloaded stdlib class. + * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] * String From c68a1c3085466436bc790e1039c98897a762c5ac Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 22:24:30 +0900 Subject: [PATCH 069/104] [DOC] [Feature #20925] Add leading logical to NEWS --- NEWS.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/NEWS.md b/NEWS.md index 1d96fc3ad2e1e0..790ea9e72630ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,25 @@ Note that each entry is kept to a minimum, see links for details. * `*nil` no longer calls `nil.to_a`, similar to how `**nil` does not call `nil.to_hash`. [[Feature #21047]] +* Logical binary operators (`||`, `&&`, `and` and `or`) at the + beginning of a line continue the previous line, like fluent dot. + The following two code are equal: + + ```ruby + if condition1 + && condition2 + ... + end + ``` + + ```ruby + if condition1 && condition2 + ... + end + ``` + + [[Feature #20925]] + ## Core classes updates Note: We're only listing outstanding class updates. @@ -285,6 +304,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 +[Feature #20925]: https://bugs.ruby-lang.org/issues/20925 [Feature #21047]: https://bugs.ruby-lang.org/issues/21047 [Bug #21049]: https://bugs.ruby-lang.org/issues/21049 [Feature #21166]: https://bugs.ruby-lang.org/issues/21166 From c5feae96217d4b4d5d270abe6a71f1b560232a9a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 22:38:38 +0900 Subject: [PATCH 070/104] Remove stale line [ci skip] --- .github/workflows/macos.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0b9d5d049d635e..70b2bc9d68d109 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -46,7 +46,6 @@ jobs: - test_task: check os: macos-15 extra_checks: [capi] - capi_check: capi - test_task: check os: macos-13 fail-fast: false From 4f4b4e3b37c884093a165801b5028c14a7de1604 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Sep 2025 20:51:40 -0400 Subject: [PATCH 071/104] Fill in lead num for blocks with `it` Fixes [Bug #21256] Co-Authored-By: Earlopain <14981592+Earlopain@users.noreply.github.com> --- prism_compile.c | 6 ++++++ test/ruby/test_syntax.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/prism_compile.c b/prism_compile.c index 74f10024cb29ac..9a6197f7cb80f1 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6740,6 +6740,12 @@ pm_compile_scope_node(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_nod body->param.flags.has_lead = true; } + // Fill in the anonymous `it` parameter, if it exists + if (scope_node->parameters && PM_NODE_TYPE_P(scope_node->parameters, PM_IT_PARAMETERS_NODE)) { + body->param.lead_num = 1; + body->param.flags.has_lead = true; + } + //********END OF STEP 3********** //********STEP 4********** diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 19cce29075d49b..94a2e03940bf5b 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -2004,6 +2004,19 @@ def test_it assert_equal(/9/, eval('9.then { /#{it}/o }')) end + def test_it_with_splat_super_method + bug21256 = '[ruby-core:121592] [Bug #21256]' + + a = Class.new do + define_method(:foo) { it } + end + b = Class.new(a) do + def foo(*args) = super + end + + assert_equal(1, b.new.foo(1), bug21256) + end + def test_value_expr_in_condition mesg = /void value expression/ assert_syntax_error("tap {a = (true ? next : break)}", mesg) From d781d69a06e7d4eef3334e44a25b02d05bad1e2d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Sep 2025 21:13:40 -0400 Subject: [PATCH 072/104] Fix prism error messages with multibyte truncation When a line is going to be displayed in an error message that contains multibyte characters, we need to respect the encoding of the source and truncate only at a character boundary, as opposed to a raw byte boundary. Fixes [Bug #21528] --- prism_compile.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/prism_compile.c b/prism_compile.c index 9a6197f7cb80f1..578e6f240f98dc 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -10627,7 +10627,26 @@ pm_parse_errors_format_line(const pm_parser_t *parser, const pm_newline_list_t * // Here we determine if we should truncate the end of the line. bool truncate_end = false; if ((column_end != 0) && ((end - (start + column_end)) >= PM_ERROR_TRUNCATE)) { - end = start + column_end + PM_ERROR_TRUNCATE; + const uint8_t *end_candidate = start + column_end + PM_ERROR_TRUNCATE; + + for (const uint8_t *ptr = start; ptr < end_candidate;) { + size_t char_width = parser->encoding->char_width(ptr, parser->end - ptr); + + // If we failed to decode a character, then just bail out and + // truncate at the fixed width. + if (char_width == 0) break; + + // If this next character would go past the end candidate, + // then we need to truncate before it. + if (ptr + char_width > end_candidate) { + end_candidate = ptr; + break; + } + + ptr += char_width; + } + + end = end_candidate; truncate_end = true; } From e74524616013c616744ebf8168f1ad57eee74a05 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Sep 2025 14:30:48 -0700 Subject: [PATCH 073/104] [ruby/prism] Revert "Merge pull request #3606 from tenderlove/clear-flags" This reverts commit https://github.com/ruby/prism/commit/4052d93cf852, reversing changes made to https://github.com/ruby/prism/commit/47143d17b3f7. https://github.com/ruby/prism/commit/f117ec6354 --- prism/prism.c | 4 ---- test/prism/result/static_literals_test.rb | 5 ----- 2 files changed, 9 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 337b77637b748f..06419d13789e45 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5279,10 +5279,6 @@ 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/test/prism/result/static_literals_test.rb b/test/prism/result/static_literals_test.rb index cc070279169aba..dcfc692897cd66 100644 --- a/test/prism/result/static_literals_test.rb +++ b/test/prism/result/static_literals_test.rb @@ -4,11 +4,6 @@ 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 f4ce5e90b2b9a4ccc7b4a0a25416c577142d2877 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 13 Sep 2025 09:53:03 -0400 Subject: [PATCH 074/104] [ruby/prism] Bump to v1.5.1 https://github.com/ruby/prism/commit/cac5118884 --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 2 +- prism/version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 74b9971a00a3e8..65305d0ec1a244 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.5.0" + spec.version = "1.5.1" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index f4cb9c438d5ed8..b18e770d9213f7 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.5.0" +#define EXPECTED_PRISM_VERSION "1.5.1" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index a5909e15bb67fe..366878f709250d 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -14,7 +14,7 @@ module Prism # The patch version of prism that we are expecting to find in the serialized # strings. - PATCH_VERSION = 0 + PATCH_VERSION = 1 # Deserialize the dumped output from a request to parse or parse_file. # diff --git a/prism/version.h b/prism/version.h index 697c7b5ad6f37e..697ba0647df1ec 100644 --- a/prism/version.h +++ b/prism/version.h @@ -19,11 +19,11 @@ /** * The patch version of the Prism library as an int. */ -#define PRISM_VERSION_PATCH 0 +#define PRISM_VERSION_PATCH 1 /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.5.0" +#define PRISM_VERSION "1.5.1" #endif From 6c9408d1b2c48087bb639bc2ad59317f0297a77e Mon Sep 17 00:00:00 2001 From: git Date: Sat, 13 Sep 2025 14:01:31 +0000 Subject: [PATCH 075/104] Update default gems list at f4ce5e90b2b9a4ccc7b4a0a25416c5 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 790ea9e72630ca..7157700816c3e8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -186,7 +186,7 @@ The following default gems are updated. * io-wait 0.3.2 * json 2.13.2 * optparse 0.7.0.dev.2 -* prism 1.5.0 +* prism 1.5.1 * psych 5.2.6 * resolv 0.6.2 * stringio 3.1.8.dev From b897a47ae965367eb154eec8abe618711a91f1c1 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 13 Sep 2025 10:09:09 -0400 Subject: [PATCH 076/104] [ruby/prism] Documentation for Prism::Translation::Parser Make it clear that it parses with the most recent version of Ruby syntax. https://github.com/ruby/prism/commit/7285d1fbab --- lib/prism/translation/parser.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index a7888f77ecced1..1ad7a193c4f18b 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -19,6 +19,13 @@ module Translation # whitequark/parser gem's syntax tree. It inherits from the base parser for # the parser gem, and overrides the parse* methods to parse with prism and # then translate. + # + # Note that this version of the parser always parses using the latest + # version of Ruby syntax supported by Prism. If you want specific version + # support, use one of the version-specific subclasses, such as + # `Prism::Translation::Parser34`. If you want to parse using the same + # version of Ruby syntax as the currently running version of Ruby, use + # `Prism::Translation::ParserCurrent`. class Parser < ::Parser::Base Diagnostic = ::Parser::Diagnostic # :nodoc: private_constant :Diagnostic @@ -77,7 +84,7 @@ def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) end def version # :nodoc: - 34 + 35 end # The default encoding for Ruby files is UTF-8. From 5e90d9e2aabebd20c9566744687e63af65a633c0 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 12 Sep 2025 10:37:34 -0400 Subject: [PATCH 077/104] Fill extra space in newly allocated object with garbage This commit fills the space that is not initialized with garbage for newly allocated objects on debug mode. --- gc.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gc.c b/gc.c index e8709dcf281a64..c412da188130e5 100644 --- a/gc.c +++ b/gc.c @@ -1010,6 +1010,18 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v RB_VM_LOCK_LEAVE_CR_LEV(cr, &lev); } +#if RGENGC_CHECK_MODE +# ifndef GC_DEBUG_SLOT_FILL_SPECIAL_VALUE +# define GC_DEBUG_SLOT_FILL_SPECIAL_VALUE 255 +# endif + + memset( + (void *)(obj + RVALUE_SIZE), + GC_DEBUG_SLOT_FILL_SPECIAL_VALUE, + rb_gc_obj_slot_size(obj) - RVALUE_SIZE + ); +#endif + return obj; } From 5cfd49fee4febedf59b53439199f02b40239ab02 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 13 Sep 2025 22:49:34 -0500 Subject: [PATCH 078/104] [ruby/erb] [DOC] Enhanced doc for ERB.new (https://github.com/ruby/erb/pull/68) https://github.com/ruby/erb/commit/9591b5d23b --- lib/erb.rb | 92 +++++++++++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/lib/erb.rb b/lib/erb.rb index c8aae4fd15239c..d4f43e0772341b 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -259,10 +259,9 @@ # # => "\n* 0,0\n\n* 0,1\n\n* 0,2\n\n* 1,0\n\n* 1,1\n\n* 1,2\n\n* 2,0\n\n* 2,1\n\n* 2,2\n\n" # ``` # -# You can use keyword argument `trim_mode` to make certain adjustments to the processing; -# see ERB.new. +# #### Shorthand Format for Execution Tags # -# In particular, you can give `trim_mode: '%'` to enable a shorthand format for execution tags; +# You can give `trim_mode: '%'` to enable a shorthand format for execution tags; # this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: # # ``` @@ -527,69 +526,62 @@ def self.version VERSION end + # :markup: markdown # - # Constructs a new ERB object with the template specified in _str_. + # :call-seq: + # ERB.new(string, trim_mode: nil, eoutvar: '_erbout') # - # An ERB object works by building a chunk of Ruby code that will output - # the completed template when run. + # Returns a new \ERB object containing the given +string+. # - # If _trim_mode_ is passed a String containing one or more of the following - # modifiers, ERB will adjust its code generation as listed: + # For details about `string`, its embedded tags, and generated results, see ERB. # - # % enables Ruby code processing for lines beginning with % - # <> omit newline for lines starting with <% and ending in %> - # > omit newline for lines ending in %> - # - omit blank lines ending in -%> + # **Keyword Argument `trim_mode`** # - # _eoutvar_ can be used to set the name of the variable ERB will build up - # its output in. This is useful when you need to run multiple ERB - # templates through the same binding and/or when you want to control where - # output ends up. Pass the name of the variable to be used inside a String. + # When keyword argument `trim_mode` has a string value, + # that value may be one of: # - # ### Example + # - `'%'`: Enable [shorthand format][shorthand format] for execution tags. + # - `'-'`: Omit each blank line ending with `'%>'`. + # - `'>'`: Omit newline for each line ending with `'%>'`. + # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # require "erb" + # The value may also be certain combinations of the above. # - # # build data class - # class Listings - # PRODUCT = { :name => "Chicken Fried Steak", - # :desc => "A well messaged pattie, breaded and fried.", - # :cost => 9.95 } + # - `'%-'`: Enable shorthand and omit each blank line ending with `'%>'`. + # - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. + # - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # attr_reader :product, :price + # **Keyword Argument `eoutvar`** # - # def initialize( product = "", price = "" ) - # @product = product - # @price = price - # end + # The string value of keyword argument `eoutvar` specifies the name of the variable + # that method #result uses to construct its result string. + # This is useful when you need to run multiple \ERB templates through the same binding + # and/or when you want to control where output ends up. # - # def build - # b = binding - # # create and run templates, filling member data variables - # ERB.new(<<~'END_PRODUCT', trim_mode: "", eoutvar: "@product").result b - # <%= PRODUCT[:name] %> - # <%= PRODUCT[:desc] %> - # END_PRODUCT - # ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b - # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> - # <%= PRODUCT[:desc] %> - # END_PRICE - # end - # end + # It's good practice to choose a variable name that begins with an underscore: `'_'`. # - # # setup template data - # listings = Listings.new - # listings.build + # Backward Compatibility # - # puts listings.product + "\n" + listings.price + # The calling sequence given above -- which is the one you should use -- + # is a simplified version of the complete formal calling sequence, + # which is: # - # _Generates_ + # ``` + # ERB.new(string, + # safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, + # trim_mode: nil, eoutvar: '_erbout') + # ``` # - # Chicken Fried Steak - # A well massaged pattie, breaded and fried. + # The second, third, and fourth positional arguments (those in the second line above) are deprecated; + # this method issues warnings if they are given. # - # Chicken Fried Steak -- 9.95 - # A well massaged pattie, breaded and fried. + # However, their values, if given, are handled thus: + # + # - `safe_level`: ignored. + # - `legacy_trim_mode: overrides keyword argument `trim_mode`. + # - `legacy_eoutvar: overrides keyword argument `eoutvar`. + # + # [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags # def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') # Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar. From 67cc574dfb1943a9cb052478cc84d0c8823d0527 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Sep 2025 12:25:25 +0900 Subject: [PATCH 079/104] Remove tests do not test Regexp methods The backslashs in these tests are parsed as escapes in string literals by the parser, but not passed to the `Regexp` methods. --- spec/ruby/core/regexp/shared/new.rb | 241 +--------------------------- 1 file changed, 1 insertion(+), 240 deletions(-) diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb index 921736d2990853..12c3d7c9c265e9 100644 --- a/spec/ruby/core/regexp/shared/new.rb +++ b/spec/ruby/core/regexp/shared/new.rb @@ -195,190 +195,14 @@ def obj.to_int() ScratchPad.record(:called) end -> { Regexp.send(@method, "\\\\") }.should_not raise_error(RegexpError) end - it "accepts a backspace followed by a character" do + it "accepts a backspace followed by a non-special character" do Regexp.send(@method, "\\N").should == /#{"\x5c"+"N"}/ end - it "accepts a one-digit octal value" do - Regexp.send(@method, "\0").should == /#{"\x00"}/ - end - - it "accepts a two-digit octal value" do - Regexp.send(@method, "\11").should == /#{"\x09"}/ - end - - it "accepts a one-digit hexadecimal value" do - Regexp.send(@method, "\x9n").should == /#{"\x09n"}/ - end - - it "accepts a two-digit hexadecimal value" do - Regexp.send(@method, "\x23").should == /#{"\x23"}/ - end - - it "interprets a digit following a two-digit hexadecimal value as a character" do - Regexp.send(@method, "\x420").should == /#{"\x420"}/ - end - it "raises a RegexpError if \\x is not followed by any hexadecimal digits" do -> { Regexp.send(@method, "\\" + "xn") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid hex escape: /\\xn/"))) end - it "accepts an escaped string interpolation" do - Regexp.send(@method, "\#{abc}").should == /#{"\#{abc}"}/ - end - - it "accepts '\\n'" do - Regexp.send(@method, "\n").should == /#{"\x0a"}/ - end - - it "accepts '\\t'" do - Regexp.send(@method, "\t").should == /#{"\x09"}/ - end - - it "accepts '\\r'" do - Regexp.send(@method, "\r").should == /#{"\x0d"}/ - end - - it "accepts '\\f'" do - Regexp.send(@method, "\f").should == /#{"\x0c"}/ - end - - it "accepts '\\v'" do - Regexp.send(@method, "\v").should == /#{"\x0b"}/ - end - - it "accepts '\\a'" do - Regexp.send(@method, "\a").should == /#{"\x07"}/ - end - - it "accepts '\\e'" do - Regexp.send(@method, "\e").should == /#{"\x1b"}/ - end - - it "accepts '\\C-\\n'" do - Regexp.send(@method, "\C-\n").should == /#{"\x0a"}/ - end - - it "accepts '\\C-\\t'" do - Regexp.send(@method, "\C-\t").should == /#{"\x09"}/ - end - - it "accepts '\\C-\\r'" do - Regexp.send(@method, "\C-\r").should == /#{"\x0d"}/ - end - - it "accepts '\\C-\\f'" do - Regexp.send(@method, "\C-\f").should == /#{"\x0c"}/ - end - - it "accepts '\\C-\\v'" do - Regexp.send(@method, "\C-\v").should == /#{"\x0b"}/ - end - - it "accepts '\\C-\\a'" do - Regexp.send(@method, "\C-\a").should == /#{"\x07"}/ - end - - it "accepts '\\C-\\e'" do - Regexp.send(@method, "\C-\e").should == /#{"\x1b"}/ - end - - it "accepts multiple consecutive '\\' characters" do - Regexp.send(@method, "\\\\\\N").should == /#{"\\\\\\"+"N"}/ - end - - it "accepts characters and escaped octal digits" do - Regexp.send(@method, "abc\076").should == /#{"abc\x3e"}/ - end - - it "accepts escaped octal digits and characters" do - Regexp.send(@method, "\076abc").should == /#{"\x3eabc"}/ - end - - it "accepts characters and escaped hexadecimal digits" do - Regexp.send(@method, "abc\x42").should == /#{"abc\x42"}/ - end - - it "accepts escaped hexadecimal digits and characters" do - Regexp.send(@method, "\x3eabc").should == /#{"\x3eabc"}/ - end - - it "accepts escaped hexadecimal and octal digits" do - Regexp.send(@method, "\061\x42").should == /#{"\x31\x42"}/ - end - - it "accepts \\u{H} for a single Unicode codepoint" do - Regexp.send(@method, "\u{f}").should == /#{"\x0f"}/ - end - - it "accepts \\u{HH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{7f}").should == /#{"\x7f"}/ - end - - it "accepts \\u{HHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{07f}").should == /#{"\x7f"}/ - end - - it "accepts \\u{HHHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{0000}").should == /#{"\x00"}/ - end - - it "accepts \\u{HHHHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{00001}").should == /#{"\x01"}/ - end - - it "accepts \\u{HHHHHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{000000}").should == /#{"\x00"}/ - end - - it "accepts characters followed by \\u{HHHH}" do - Regexp.send(@method, "abc\u{3042}").should == /#{"abc\u3042"}/ - end - - it "accepts \\u{HHHH} followed by characters" do - Regexp.send(@method, "\u{3042}abc").should == /#{"\u3042abc"}/ - end - - it "accepts escaped hexadecimal digits followed by \\u{HHHH}" do - Regexp.send(@method, "\x42\u{3042}").should == /#{"\x42\u3042"}/ - end - - it "accepts escaped octal digits followed by \\u{HHHH}" do - Regexp.send(@method, "\056\u{3042}").should == /#{"\x2e\u3042"}/ - end - - it "accepts a combination of escaped octal and hexadecimal digits and \\u{HHHH}" do - Regexp.send(@method, "\056\x42\u{3042}\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/ - end - - it "accepts \\uHHHH for a single Unicode codepoint" do - Regexp.send(@method, "\u3042").should == /#{"\u3042"}/ - end - - it "accepts characters followed by \\uHHHH" do - Regexp.send(@method, "abc\u3042").should == /#{"abc\u3042"}/ - end - - it "accepts \\uHHHH followed by characters" do - Regexp.send(@method, "\u3042abc").should == /#{"\u3042abc"}/ - end - - it "accepts escaped hexadecimal digits followed by \\uHHHH" do - Regexp.send(@method, "\x42\u3042").should == /#{"\x42\u3042"}/ - end - - it "accepts escaped octal digits followed by \\uHHHH" do - Regexp.send(@method, "\056\u3042").should == /#{"\x2e\u3042"}/ - end - - it "accepts a combination of escaped octal and hexadecimal digits and \\uHHHH" do - Regexp.send(@method, "\056\x42\u3042\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/ - end - - it "accepts a multiple byte character which need not be escaped" do - Regexp.send(@method, "\§").should == /#{"§"}/ - end - it "raises a RegexpError if less than four digits are given for \\uHHHH" do -> { Regexp.send(@method, "\\" + "u304") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid Unicode escape: /\\u304/"))) end @@ -433,69 +257,6 @@ def obj.to_int() ScratchPad.record(:called) end describe :regexp_new_string_binary, shared: true do describe "with escaped characters" do - it "accepts a three-digit octal value" do - Regexp.send(@method, "\315").should == /#{"\xcd"}/ - end - - it "interprets a digit following a three-digit octal value as a character" do - Regexp.send(@method, "\3762").should == /#{"\xfe2"}/ - end - - it "accepts '\\M-\\n'" do - Regexp.send(@method, "\M-\n").should == /#{"\x8a"}/ - end - - it "accepts '\\M-\\t'" do - Regexp.send(@method, "\M-\t").should == /#{"\x89"}/ - end - - it "accepts '\\M-\\r'" do - Regexp.send(@method, "\M-\r").should == /#{"\x8d"}/ - end - - it "accepts '\\M-\\f'" do - Regexp.send(@method, "\M-\f").should == /#{"\x8c"}/ - end - - it "accepts '\\M-\\v'" do - Regexp.send(@method, "\M-\v").should == /#{"\x8b"}/ - end - - it "accepts '\\M-\\a'" do - Regexp.send(@method, "\M-\a").should == /#{"\x87"}/ - end - - it "accepts '\\M-\\e'" do - Regexp.send(@method, "\M-\e").should == /#{"\x9b"}/ - end - - it "accepts '\\M-\\C-\\n'" do - Regexp.send(@method, "\M-\C-\n").should == /#{"\x8a"}/ - end - - it "accepts '\\M-\\C-\\t'" do - Regexp.send(@method, "\M-\C-\t").should == /#{"\x89"}/ - end - - it "accepts '\\M-\\C-\\r'" do - Regexp.send(@method, "\M-\C-\r").should == /#{"\x8d"}/ - end - - it "accepts '\\M-\\C-\\f'" do - Regexp.send(@method, "\M-\C-\f").should == /#{"\x8c"}/ - end - - it "accepts '\\M-\\C-\\v'" do - Regexp.send(@method, "\M-\C-\v").should == /#{"\x8b"}/ - end - - it "accepts '\\M-\\C-\\a'" do - Regexp.send(@method, "\M-\C-\a").should == /#{"\x87"}/ - end - - it "accepts '\\M-\\C-\\e'" do - Regexp.send(@method, "\M-\C-\e").should == /#{"\x9b"}/ - end end end From 52283b44ffff21a5f79c084bbb5bb6a256842cbd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Sep 2025 14:15:55 +0900 Subject: [PATCH 080/104] Compile `RGENGC_OBJ_INFO` case statically Make this macro condition as compile-time constant instead of a preprocess-time constant, and compile the body always. --- gc.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/gc.c b/gc.c index c412da188130e5..0433da2e67c282 100644 --- a/gc.c +++ b/gc.c @@ -4965,12 +4965,6 @@ rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) #undef APPEND_F #undef BUFF_ARGS -#if RGENGC_OBJ_INFO -#define OBJ_INFO_BUFFERS_NUM 10 -#define OBJ_INFO_BUFFERS_SIZE 0x100 -static rb_atomic_t obj_info_buffers_index = 0; -static char obj_info_buffers[OBJ_INFO_BUFFERS_NUM][OBJ_INFO_BUFFERS_SIZE]; - /* Increments *var atomically and resets *var to 0 when maxval is * reached. Returns the wraparound old *var value (0...maxval). */ static rb_atomic_t @@ -4988,17 +4982,18 @@ atomic_inc_wraparound(rb_atomic_t *var, const rb_atomic_t maxval) static const char * obj_info(VALUE obj) { - rb_atomic_t index = atomic_inc_wraparound(&obj_info_buffers_index, OBJ_INFO_BUFFERS_NUM); - char *const buff = obj_info_buffers[index]; - return rb_raw_obj_info(buff, OBJ_INFO_BUFFERS_SIZE, obj); -} -#else -static const char * -obj_info(VALUE obj) -{ + if (RGENGC_OBJ_INFO) { + static struct { + rb_atomic_t index; + char buffers[10][0x100]; + } info = {0}; + + rb_atomic_t index = atomic_inc_wraparound(&info.index, numberof(info.buffers)); + char *const buff = info.buffers[index]; + return rb_raw_obj_info(buff, sizeof(info.buffers[0]), obj); + } return obj_type_name(obj); } -#endif /* ------------------------ Extended allocator ------------------------ From 4491c6737d686a426865f8bf518b19a6857907da Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Sep 2025 14:28:57 +0900 Subject: [PATCH 081/104] Remove `RGENGC_OBJ_INFO` compilations check Now it is always compiled (and will be optimized away). --- .github/workflows/compilers.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index cf456bc5bb88e6..3bb8220d8282f1 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -281,7 +281,6 @@ jobs: - { uses: './.github/actions/compilers', name: 'GC_PROFILE_MORE_DETAIL', with: { cppflags: '-DGC_PROFILE_MORE_DETAIL' } } - { uses: './.github/actions/compilers', name: 'MALLOC_ALLOCATED_SIZE_CHECK', with: { cppflags: '-DMALLOC_ALLOCATED_SIZE_CHECK' } } - { uses: './.github/actions/compilers', name: 'RGENGC_ESTIMATE_OLDMALLOC', with: { cppflags: '-DRGENGC_ESTIMATE_OLDMALLOC' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_OBJ_INFO', with: { cppflags: '-DRGENGC_OBJ_INFO' } } - { uses: './.github/actions/compilers', name: 'RGENGC_PROFILE', with: { cppflags: '-DRGENGC_PROFILE' } } compileC: From 214ba4074d11ba3e610546700298caba89a35071 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Sep 2025 17:25:11 +0900 Subject: [PATCH 082/104] win32: Also ruby/confg.h depends on the ABI version header --- win32/Makefile.sub | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index ade48d5e7575c2..c91c05584e6331 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -649,7 +649,8 @@ config.status: nul guard = INCLUDE_RUBY_CONFIG_H -$(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub +$(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub \ + $(ABI_VERSION_HDR) @echo Creating config.h !if !exist("$(arch_hdrdir)") @md $(arch_hdrdir:/=\) From c4ac3e987bf05a743ef9dcfb653bbb7e5b8637df Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Sep 2025 20:57:56 +0900 Subject: [PATCH 083/104] Save CAPI extensions cache not-cached master only --- .github/actions/capiext/action.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index a41202c7a4053a..c17069f97dae20 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -35,8 +35,8 @@ runs: echo DLEXT=$DLEXT >> $GITHUB_OUTPUT working-directory: ${{ inputs.builddir }} - - name: CAPI extensions cache - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - name: Restore previous CAPI extensions + uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 id: cache with: path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ @@ -50,11 +50,24 @@ runs: touch spec/ruby/optional/capi/ext/*.$DLEXT [ ! -f spec/ruby/optional/capi/ext/\*.$DLEXT ] ${{ inputs.make }} SPECOPTS=optional/capi test-spec + rm -f spec/ruby/optional/capi/ext/*.c env: DLEXT: ${{ steps.config.outputs.DLEXT }} working-directory: ${{ inputs.builddir }} if: ${{ steps.cache.outputs.cache-hit }} + - name: Save CAPI extensions + uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ + key: ${{ steps.config.outputs.key }} + if: >- + ${{true + && steps.cache.outcome == 'success' + && ! steps.cache.outputs.cache-hit + && github.ref_name == 'master' + }} + - shell: bash run: | echo "::error::Change from ${prev} detected; bump up ABI version" From f18af456577a870600f2f766aed8ac6b77af9720 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Sep 2025 21:49:35 +0900 Subject: [PATCH 084/104] Set `$extmk` in extmk.rb Get rid of `File.identical?` on a WebDAV-mounted drive. --- ext/extmk.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/extmk.rb b/ext/extmk.rb index 39cbce1bc9fe68..888eba6c676d41 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -43,6 +43,7 @@ def self.target_rbconfig $topdir = "." $top_srcdir = srcdir +$extmk = true inplace = File.identical?($top_srcdir, $topdir) $" << "mkmf.rb" From cb35cc630c1a0f343623317bcbc69c62f89b3be6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 14:56:28 +0900 Subject: [PATCH 085/104] Make PRISM_BUILD_DIR even when baseruby is available It is needed before updating the timestamp for prism/srcs.mk. --- prism/srcs.mk | 3 +-- prism/srcs.mk.in | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/prism/srcs.mk b/prism/srcs.mk index 565d793cc0a210..ff4ddff0722e32 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -4,9 +4,8 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs uncommon.mk: prism/.srcs.mk.time -prism/.srcs.mk.time: +prism/.srcs.mk.time: $(order_only) $(PRISM_BUILD_DIR)/.time prism/$(HAVE_BASERUBY:no=.srcs.mk.time): - mkdir -p $(@D) touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ diff --git a/prism/srcs.mk.in b/prism/srcs.mk.in index 0b921b7f0bb07d..e337eff8ea00e5 100644 --- a/prism/srcs.mk.in +++ b/prism/srcs.mk.in @@ -12,9 +12,8 @@ PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml srcs <%=%><%=mk%>: prism/.srcs.mk.time -prism/.srcs.mk.time: +prism/.srcs.mk.time: $(order_only) $(PRISM_BUILD_DIR)/.time prism/$(HAVE_BASERUBY:no=.srcs.mk.time): - mkdir -p $(@D) touch $@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/templates/template.rb \ From 9f024c2240b10408a03634b708b3a98a07126ffc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 16:00:02 +0900 Subject: [PATCH 086/104] Share `TestObject::ToStrCounter` class --- test/ruby/test_object.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 9074e54df5ed50..cccd7359e1108e 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -280,6 +280,12 @@ def test_methods_prepend_singleton assert_equal([:foo], k.private_methods(false)) end + class ToStrCounter + def initialize(str = "@foo") @str = str; @count = 0; end + def to_str; @count += 1; @str; end + def count; @count; end + end + def test_instance_variable_get o = Object.new o.instance_eval { @foo = :foo } @@ -291,9 +297,7 @@ def test_instance_variable_get assert_raise(NameError) { o.instance_variable_get("bar") } assert_raise(TypeError) { o.instance_variable_get(1) } - n = Object.new - def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end - def n.count; @count; end + n = ToStrCounter.new assert_equal(:foo, o.instance_variable_get(n)) assert_equal(1, n.count) end @@ -308,9 +312,7 @@ def test_instance_variable_set assert_raise(NameError) { o.instance_variable_set("bar", 1) } assert_raise(TypeError) { o.instance_variable_set(1, 1) } - n = Object.new - def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end - def n.count; @count; end + n = ToStrCounter.new o.instance_variable_set(n, :bar) assert_equal(:bar, o.instance_eval { @foo }) assert_equal(1, n.count) @@ -327,9 +329,7 @@ def test_instance_variable_defined assert_raise(NameError) { o.instance_variable_defined?("bar") } assert_raise(TypeError) { o.instance_variable_defined?(1) } - n = Object.new - def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end - def n.count; @count; end + n = ToStrCounter.new assert_equal(true, o.instance_variable_defined?(n)) assert_equal(1, n.count) end From 6d2f93f17022bb89ffed7c8f12ad6a8ef048e2b2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 16:56:23 +0900 Subject: [PATCH 087/104] [ruby/date] Suppress maybe-uninitialized warning by gcc-13 https://github.com/ruby/date/commit/afaa4a997b --- ext/date/date_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index dbee067f6baa8e..457838e41f8d55 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -3974,7 +3974,7 @@ rt_complete_frags(VALUE klass, VALUE hash) rb_gc_register_mark_object(tab); } - k = Qnil; + k = a = Qnil; { long i, eno = 0; From e19bb99347cc45b27831b73736b0c3baf250e5e6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 17:11:41 +0900 Subject: [PATCH 088/104] CI: Remove "combo build tests" in Compilations [ci skip] These tests are run in the dedicated workflows for JITs. --- .github/workflows/compilers.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 3bb8220d8282f1..c53be6f410c5d1 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -296,9 +296,6 @@ jobs: - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' } } - { uses: './.github/actions/compilers', name: 'VM_DEBUG_VERIFY_METHOD_CACHE', with: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } } - - { uses: './.github/actions/compilers', name: 'enable-yjit', with: { append_configure: '--enable-yjit' } } - - { uses: './.github/actions/compilers', name: 'enable-{y,z}jit', with: { append_configure: '--enable-yjit --enable-zjit' } } - - { uses: './.github/actions/compilers', name: 'enable-{y,z}jit=dev', with: { append_configure: '--enable-yjit=dev --enable-zjit' } } - { uses: './.github/actions/compilers', name: 'YJIT_FORCE_ENABLE', with: { cppflags: '-DYJIT_FORCE_ENABLE' } } - { uses: './.github/actions/compilers', name: 'UNIVERSAL_PARSER', with: { cppflags: '-DUNIVERSAL_PARSER' } } From 8ad5a0a8d92c1e8da7d1c26b1bc2495712cf41f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 15 Sep 2025 11:51:12 +0200 Subject: [PATCH 089/104] [DOC] Fix typos in comments --- include/ruby/internal/core/robject.h | 2 +- pathname_builtin.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ruby/internal/core/robject.h b/include/ruby/internal/core/robject.h index 4892faab2b1adf..99f6470ac15c81 100644 --- a/include/ruby/internal/core/robject.h +++ b/include/ruby/internal/core/robject.h @@ -55,7 +55,7 @@ */ enum ruby_robject_flags { /** - * This flag has marks that the object's instance variables are stored in an + * This flag marks that the object's instance variables are stored in an * external heap buffer. * Normally, instance variable references are stored inside the object slot, * but if it overflow, Ruby may have to allocate a separate buffer and spills diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 52b7003a64b8bf..16ed219ec38c06 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -321,7 +321,7 @@ def sub_ext(repl) end if File.dirname('A:') == 'A:.' # DOSish drive letter - # Regexp that matches an absoltute path. + # Regexp that matches an absolute path. ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/ else ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/ From 9299cf31b1607859daf89772b777f19f426bed80 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 15 Sep 2025 08:39:30 +0200 Subject: [PATCH 090/104] [ruby/prism] Fix warn polyfill when no uplevel is provided An unspecified uplevel is not the same as an uplevel of 1: ``` $ irb irb(main):001> warn("foo") foo => nil irb(main):002> warn("foo", uplevel: 1) /home/user/.rbenv/versions/2.7.8/lib/ruby/gems/2.7.0/gems/irb-1.14.0/lib/irb/workspace.rb:121: warning: foo => nil ``` https://github.com/ruby/prism/commit/dcedd14357 --- lib/prism/polyfill/warn.rb | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/lib/prism/polyfill/warn.rb b/lib/prism/polyfill/warn.rb index 560380d30807e8..76a4264623ba21 100644 --- a/lib/prism/polyfill/warn.rb +++ b/lib/prism/polyfill/warn.rb @@ -7,17 +7,14 @@ Kernel.prepend( Module.new { def warn(*msgs, uplevel: nil, category: nil) # :nodoc: - uplevel = - case uplevel - when nil - 1 - when Integer - uplevel + 1 - else - uplevel.to_int + 1 - end - - super(*msgs, uplevel: uplevel) + case uplevel + when nil + super(*msgs) + when Integer + super(*msgs, uplevel: uplevel + 1) + else + super(*msgs, uplevel: uplevel.to_int + 1) + end end } ) @@ -25,17 +22,14 @@ def warn(*msgs, uplevel: nil, category: nil) # :nodoc: Object.prepend( Module.new { def warn(*msgs, uplevel: nil, category: nil) # :nodoc: - uplevel = - case uplevel - when nil - 1 - when Integer - uplevel + 1 - else - uplevel.to_int + 1 - end - - super(*msgs, uplevel: uplevel) + case uplevel + when nil + super(*msgs) + when Integer + super(*msgs, uplevel: uplevel + 1) + else + super(*msgs, uplevel: uplevel.to_int + 1) + end end } ) From d89b45339223035433a8036c371f86e12134d36a Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 15 Sep 2025 08:10:13 -0500 Subject: [PATCH 091/104] [DOC] Tweaks for String#oct --- string.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/string.c b/string.c index 8d27248020cded..834641cfdbf0c1 100644 --- a/string.c +++ b/string.c @@ -10744,20 +10744,79 @@ rb_str_hex(VALUE str) * call-seq: * oct -> integer * - * Interprets the leading substring of +self+ as a string of octal digits - * (with an optional sign) and returns the corresponding number; - * returns zero if there is no such leading substring: + * Interprets the leading substring of +self+ as octal, binary, decimal, or hexadecimal, possibly signed; + * returns their value as an integer. * - * '123'.oct # => 83 - * '-377'.oct # => -255 - * '0377non-numeric'.oct # => 255 - * 'non-numeric'.oct # => 0 + * In brief: * - * If +self+ starts with 0, radix indicators are honored; - * see Kernel#Integer. + * # Interpreted as octal. + * '777'.oct # => 511 + * '777x'.oct # => 511 + * '0777'.oct # => 511 + * '0o777'.oct # => 511 + * '-777'.oct # => -511 + * # Not interpreted as octal. + * '0b111'.oct # => 7 # Interpreted as binary. + * '0d999'.oct # => 999 # Interpreted as decimal. + * '0xfff'.oct # => 4095 # Interpreted as hexadecimal. * - * Related: String#hex. + * The leading substring is interpreted as octal when it begins with: * + * - One or more character representing octal digits + * (each in the range '0'..'7'); + * the string to be interpreted ends at the first character that does not represent an octal digit: + * + * '7'.oct @ => 7 + * '11'.oct # => 9 + * '777'.oct # => 511 + * '0777'.oct # => 511 + * '7778'.oct # => 511 + * '777x'.oct # => 511 + * + * - '0o', followed by one or more octal digits: + * + * '0o777'.oct # => 511 + * '0o7778'.oct # => 511 + * + * The leading substring is _not_ interpreted as octal when it begins with: + * + * - '0b', followed by one or more characters representing binary digits + * (each in the range '0'..'1'); + * the string to be interpreted ends at the first character that does not represent a binary digit. + * the string is interpreted as binary digits (base 2): + * + * '0b111'.oct # => 7 + * '0b1112'.oct # => 7 + * + * - '0d', followed by one or more characters representing decimal digits + * (each in the range '0'..'9'); + * the string to be interpreted ends at the first character that does not represent a decimal digit. + * the string is interpreted as decimal digits (base 10): + * + * '0d999'.oct # => 999 + * '0d999x'.oct # => 999 + * + * - '0x', followed by one or more characters representing hexadecimal digits + * (each in one of the ranges '0'..'9', 'a'..'f', or 'A'..'F'); + * the string to be interpreted ends at the first character that does not represent a hexadecimal digit. + * the string is interpreted as hexadecimal digits (base 16): + * + * '0xfff'.oct # => 4095 + * '0xfffg'.oct # => 4095 + * + * Any of the above may prefixed with '-', which negates the interpreted value: + * + * '-777'.oct # => -511 + * '-0777'.oct # => -511 + * '-0b111'.oct # => -7 + * '-0xfff'.oct # => -4095 + * + * For any substring not described above, returns zero: + * + * 'foo'.oct # => 0 + * ''.oct # => 0 + * + * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE From 70210acab046239beedf880edf0330e3cb389ce8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 22:10:14 +0900 Subject: [PATCH 092/104] [ruby/optparse] Prefer `Proc` over `Method` The performances are: block > proc > method object. https://github.com/ruby/optparse/commit/9ec5d1d582 --- lib/optparse.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 06e33db1f533c4..ea6844b9558e5c 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1855,7 +1855,7 @@ def permute(*argv, **keywords) # def permute!(argv = default_argv, **keywords) nonopts = [] - order!(argv, **keywords, &nonopts.method(:<<)) + order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end @@ -1908,13 +1908,16 @@ def getopts(*args, symbolize_names: false, **keywords) single_options, *long_options = *args result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val - result[opt] = nil + setter[opt, nil] define("-#{opt} VAL") else - result[opt] = false + setter[opt, false] define("-#{opt}") end end if single_options @@ -1923,16 +1926,16 @@ def getopts(*args, symbolize_names: false, **keywords) arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val - result[opt] = val.empty? ? nil : val + setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else - result[opt] = false + setter[opt, false] define("--#{opt}", *[desc].compact) end end - parse_in_order(argv, result.method(:[]=), **keywords) - symbolize_names ? result.transform_keys(&:to_sym) : result + parse_in_order(argv, setter, **keywords) + result end # @@ -1982,7 +1985,7 @@ def complete(typ, opt, icase = false, *pat) # :nodoc: visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption - raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end private :complete @@ -2273,9 +2276,10 @@ def recover(argv) argv end + DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + array.delete_if {|bt| bt.start_with?(DIR)} end array end From b0ce1fd549a3227ef4d9f65e4cdf0df93e4adeb0 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 13 Sep 2025 14:10:57 -0400 Subject: [PATCH 093/104] Combine rb_imemo_tmpbuf_auto_free_pointer and rb_imemo_tmpbuf_new --- dir.c | 2 +- imemo.c | 15 +++------------ internal/imemo.h | 8 +++----- process.c | 6 +++--- util.c | 2 +- 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/dir.c b/dir.c index b934f2795c90e0..25ed59c668fab6 100644 --- a/dir.c +++ b/dir.c @@ -1480,7 +1480,7 @@ rb_dir_getwd_ospath(void) VALUE cwd; VALUE path_guard; - path_guard = rb_imemo_tmpbuf_auto_free_pointer(); + path_guard = rb_imemo_tmpbuf_new(); path = ruby_getcwd(); rb_imemo_tmpbuf_set_ptr(path_guard, path); #ifdef __APPLE__ diff --git a/imemo.c b/imemo.c index 02cba387bd22be..1bef7a71a69e77 100644 --- a/imemo.c +++ b/imemo.c @@ -48,23 +48,14 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) return (VALUE)obj; } -static rb_imemo_tmpbuf_t * -rb_imemo_tmpbuf_new(void) -{ - return IMEMO_NEW(rb_imemo_tmpbuf_t, imemo_tmpbuf, 0); -} - void * rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) { - void *ptr; - rb_imemo_tmpbuf_t *tmpbuf; - /* Keep the order; allocate an empty imemo first then xmalloc, to * get rid of potential memory leak */ - tmpbuf = rb_imemo_tmpbuf_new(); + rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); *store = (VALUE)tmpbuf; - ptr = ruby_xmalloc(size); + void *ptr = ruby_xmalloc(size); tmpbuf->ptr = ptr; tmpbuf->cnt = cnt; @@ -97,7 +88,7 @@ rb_free_tmp_buffer(volatile VALUE *store) rb_imemo_tmpbuf_t * rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt) { - rb_imemo_tmpbuf_t *tmpbuf = rb_imemo_tmpbuf_new(); + rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); tmpbuf->ptr = buf; tmpbuf->next = old_heap; tmpbuf->cnt = cnt; diff --git a/internal/imemo.h b/internal/imemo.h index de39102432ea84..3673190809e86a 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -138,10 +138,8 @@ static inline enum imemo_type imemo_type(VALUE imemo); static inline int imemo_type_p(VALUE imemo, enum imemo_type imemo_type); static inline bool imemo_throw_data_p(VALUE imemo); static inline struct vm_ifunc *rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data); -static inline VALUE rb_imemo_tmpbuf_auto_free_pointer(void); static inline void *RB_IMEMO_TMPBUF_PTR(VALUE v); static inline void *rb_imemo_tmpbuf_set_ptr(VALUE v, void *ptr); -static inline VALUE rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(VALUE str); static inline void MEMO_V1_SET(struct MEMO *m, VALUE v); static inline void MEMO_V2_SET(struct MEMO *m, VALUE v); @@ -201,7 +199,7 @@ rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data) } static inline VALUE -rb_imemo_tmpbuf_auto_free_pointer(void) +rb_imemo_tmpbuf_new(void) { return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); } @@ -220,7 +218,7 @@ rb_imemo_tmpbuf_set_ptr(VALUE v, void *ptr) } static inline VALUE -rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(VALUE str) +rb_imemo_tmpbuf_new_from_an_RString(VALUE str) { const void *src; VALUE imemo; @@ -230,7 +228,7 @@ rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(VALUE str) StringValue(str); /* create tmpbuf to keep the pointer before xmalloc */ - imemo = rb_imemo_tmpbuf_auto_free_pointer(); + imemo = rb_imemo_tmpbuf_new(); tmpbuf = (rb_imemo_tmpbuf_t *)imemo; len = RSTRING_LEN(str); src = RSTRING_PTR(str); diff --git a/process.c b/process.c index da9ce74027ce3c..0fb727db8af9f8 100644 --- a/process.c +++ b/process.c @@ -2635,7 +2635,7 @@ rb_exec_fillarg(VALUE prog, int argc, VALUE *argv, VALUE env, VALUE opthash, VAL } rb_str_buf_cat(argv_str, (char *)&null, sizeof(null)); /* terminator for execve. */ eargp->invoke.cmd.argv_str = - rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(argv_str); + rb_imemo_tmpbuf_new_from_an_RString(argv_str); } RB_GC_GUARD(execarg_obj); } @@ -2726,7 +2726,7 @@ open_func(void *ptr) static void rb_execarg_allocate_dup2_tmpbuf(struct rb_execarg *eargp, long len) { - VALUE tmpbuf = rb_imemo_tmpbuf_auto_free_pointer(); + VALUE tmpbuf = rb_imemo_tmpbuf_new(); rb_imemo_tmpbuf_set_ptr(tmpbuf, ruby_xmalloc(run_exec_dup2_tmpbuf_size(len))); eargp->dup2_tmpbuf = tmpbuf; } @@ -2830,7 +2830,7 @@ rb_execarg_parent_start1(VALUE execarg_obj) p = NULL; rb_str_buf_cat(envp_str, (char *)&p, sizeof(p)); eargp->envp_str = - rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(envp_str); + rb_imemo_tmpbuf_new_from_an_RString(envp_str); eargp->envp_buf = envp_buf; /* diff --git a/util.c b/util.c index 3e8ae590a8d54e..4caa324849af31 100644 --- a/util.c +++ b/util.c @@ -529,7 +529,7 @@ ruby_strdup(const char *str) char * ruby_getcwd(void) { - VALUE guard = rb_imemo_tmpbuf_auto_free_pointer(); + VALUE guard = rb_imemo_tmpbuf_new(); int size = 200; char *buf = xmalloc(size); From 23c04a9be7a842c3897ee7a8c926353b607bf7dc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 23:18:17 +0900 Subject: [PATCH 094/104] [DOC] Markup as markdown --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7157700816c3e8..7534539a21d2c9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -63,7 +63,7 @@ Note: We're only listing outstanding class updates. * IO - * `IO.select` accepts +Float::INFINITY+ as a timeout argument. + * `IO.select` accepts `Float::INFINITY` as a timeout argument. [[Feature #20610]] * Math From 6c34880111a0978407224fc353c35f2a3a0a981a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 15 Sep 2025 10:18:46 -0400 Subject: [PATCH 095/104] [ruby/prism] Fix character literal forced encoding If a character literal was followed by a string concatenation, then the forced encoding of the string concatenation could accidentally overwrite the explicit encoding of the character literal. We now handle this properly. https://github.com/ruby/prism/commit/125c375d74 --- prism/prism.c | 30 ++++++++++++++--------- test/prism/fixtures/character_literal.txt | 2 ++ test/prism/ruby/ruby_parser_test.rb | 1 + 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 test/prism/fixtures/character_literal.txt diff --git a/prism/prism.c b/prism/prism.c index 06419d13789e45..2e202c37456ea5 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -18491,20 +18491,28 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b return (pm_node_t *) node; } case PM_TOKEN_CHARACTER_LITERAL: { - parser_lex(parser); - - pm_token_t opening = parser->previous; - opening.type = PM_TOKEN_STRING_BEGIN; - opening.end = opening.start + 1; - - pm_token_t content = parser->previous; - content.type = PM_TOKEN_STRING_CONTENT; - content.start = content.start + 1; - pm_token_t closing = not_provided(parser); - pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &content, &closing); + pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string( + parser, + &(pm_token_t) { + .type = PM_TOKEN_STRING_BEGIN, + .start = parser->current.start, + .end = parser->current.start + 1 + }, + &(pm_token_t) { + .type = PM_TOKEN_STRING_CONTENT, + .start = parser->current.start + 1, + .end = parser->current.end + }, + &closing + ); + pm_node_flag_set(node, parse_unescaped_encoding(parser)); + // Skip past the character literal here, since now we have handled + // parser->explicit_encoding correctly. + parser_lex(parser); + // Characters can be followed by strings in which case they are // automatically concatenated. if (match1(parser, PM_TOKEN_STRING_BEGIN)) { diff --git a/test/prism/fixtures/character_literal.txt b/test/prism/fixtures/character_literal.txt new file mode 100644 index 00000000000000..920332123f13ac --- /dev/null +++ b/test/prism/fixtures/character_literal.txt @@ -0,0 +1,2 @@ +# encoding: Windows-31J +p ?\u3042"" diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index bcaed7979150bc..b21ad81391ed1e 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -16,6 +16,7 @@ module Prism class RubyParserTest < TestCase todos = [ + "character_literal.txt", "encoding_euc_jp.txt", "regex_char_width.txt", "seattlerb/masgn_colon3.txt", From b08573c8150c97822b05b743d5ebb8c4fff5315f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:43:34 +0200 Subject: [PATCH 096/104] [ruby/prism] Fix back reference for ruby_parser on Ruby 2.7 Symbol#name is only a thing since Ruby 3.0 https://github.com/ruby/prism/commit/2de82b15fc --- lib/prism/translation/ruby_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index ac538a2e97ae43..2ca7da0bf2a5d5 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -152,7 +152,7 @@ def visit_assoc_splat_node(node) # ^^ # ``` def visit_back_reference_read_node(node) - s(node, :back_ref, node.name.name.delete_prefix("$").to_sym) + s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym) end # ``` From 61df125325c4f5cad0fd63a831f7afd4c3e71dba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 00:11:14 +0900 Subject: [PATCH 097/104] [DOC] Markup code in `Float::MIN` document --- numeric.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index 89cff8a730fc9c..de5b02aaf9eb41 100644 --- a/numeric.c +++ b/numeric.c @@ -6455,7 +6455,7 @@ Init_Numeric(void) * * If the platform supports denormalized numbers, * there are numbers between zero and Float::MIN. - * 0.0.next_float returns the smallest positive floating point number + * +0.0.next_float+ returns the smallest positive floating point number * including denormalized numbers. */ rb_define_const(rb_cFloat, "MIN", DBL2NUM(DBL_MIN)); From 1e3e04cd657c35fdd8d95096195d6b72b64e516c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 15 Sep 2025 10:47:14 -0400 Subject: [PATCH 098/104] Move rb_imemo_tmpbuf_new to imemo.c --- imemo.c | 6 ++++++ internal/imemo.h | 7 +------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/imemo.c b/imemo.c index 1bef7a71a69e77..1fc49c434af75e 100644 --- a/imemo.c +++ b/imemo.c @@ -48,6 +48,12 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) return (VALUE)obj; } +VALUE +rb_imemo_tmpbuf_new(void) +{ + return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); +} + void * rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) { diff --git a/internal/imemo.h b/internal/imemo.h index 3673190809e86a..de617d94c1bf5c 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -132,6 +132,7 @@ struct MEMO { #ifndef RUBY_RUBYPARSER_H typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif +VALUE rb_imemo_tmpbuf_new(void); rb_imemo_tmpbuf_t *rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); @@ -198,12 +199,6 @@ rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data) return rb_vm_ifunc_new(func, data, 0, UNLIMITED_ARGUMENTS); } -static inline VALUE -rb_imemo_tmpbuf_new(void) -{ - return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); -} - static inline void * RB_IMEMO_TMPBUF_PTR(VALUE v) { From 7dd9c76ad46af63f76e0df243f76a1720f54d50d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 15 Sep 2025 10:50:01 -0400 Subject: [PATCH 099/104] Make imemo_tmpbuf not write-barrier protected imemo_tmpbuf is not write-barrier protected and uses mark maybe to mark the buffer it holds. The normal rb_imemo_new creates a write-barrier protected object which can make the tmpbuf miss marking references. --- imemo.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imemo.c b/imemo.c index 1fc49c434af75e..7cec33bc1edf00 100644 --- a/imemo.c +++ b/imemo.c @@ -51,7 +51,10 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) VALUE rb_imemo_tmpbuf_new(void) { - return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); + VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); + NEWOBJ_OF(obj, rb_imemo_tmpbuf_t, 0, flags, sizeof(rb_imemo_tmpbuf_t), NULL); + + return (VALUE)obj; } void * From e8fdfb49105bd90dc3a5fdfec10539560bd700ca Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 16:16:29 -0400 Subject: [PATCH 100/104] del other workflow --- .github/workflows/annocheck.yml | 110 ------- .github/workflows/auto_request_review.yml | 20 -- .github/workflows/baseruby.yml | 74 ----- .github/workflows/bundled_gems.yml | 184 ------------ .github/workflows/check_dependencies.yml | 59 ---- .github/workflows/check_misc.yml | 118 -------- .github/workflows/codeql-analysis.yml | 121 -------- .github/workflows/compilers.yml | 333 --------------------- .github/workflows/cygwin.yml | 65 ---- .github/workflows/default_gems.yml | 96 ------ .github/workflows/dependabot_automerge.yml | 32 -- .github/workflows/labeler.yml | 12 - .github/workflows/macos.yml | 205 ------------- .github/workflows/mingw.yml | 221 -------------- .github/workflows/modgc.yml | 176 ----------- .github/workflows/parse_y.yml | 100 ------- .github/workflows/pr-playground.yml | 127 -------- .github/workflows/publish.yml | 18 -- .github/workflows/release.yml | 107 ------- .github/workflows/rust-warnings.yml | 51 ---- .github/workflows/scorecards.yml | 78 ----- .github/workflows/spec_guards.yml | 65 ---- .github/workflows/ubuntu-ibm.yml | 183 ----------- .github/workflows/ubuntu.yml | 183 ----------- .github/workflows/wasm.yml | 179 ----------- .github/workflows/windows.yml | 220 -------------- .github/workflows/wsl.yml | 70 ----- 27 files changed, 3207 deletions(-) delete mode 100644 .github/workflows/annocheck.yml delete mode 100644 .github/workflows/auto_request_review.yml delete mode 100644 .github/workflows/baseruby.yml delete mode 100644 .github/workflows/bundled_gems.yml delete mode 100644 .github/workflows/check_dependencies.yml delete mode 100644 .github/workflows/check_misc.yml delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 .github/workflows/compilers.yml delete mode 100644 .github/workflows/cygwin.yml delete mode 100644 .github/workflows/default_gems.yml delete mode 100644 .github/workflows/dependabot_automerge.yml delete mode 100644 .github/workflows/labeler.yml delete mode 100644 .github/workflows/macos.yml delete mode 100644 .github/workflows/mingw.yml delete mode 100644 .github/workflows/modgc.yml delete mode 100644 .github/workflows/parse_y.yml delete mode 100644 .github/workflows/pr-playground.yml delete mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/rust-warnings.yml delete mode 100644 .github/workflows/scorecards.yml delete mode 100644 .github/workflows/spec_guards.yml delete mode 100644 .github/workflows/ubuntu-ibm.yml delete mode 100644 .github/workflows/ubuntu.yml delete mode 100644 .github/workflows/wasm.yml delete mode 100644 .github/workflows/windows.yml delete mode 100644 .github/workflows/wsl.yml diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml deleted file mode 100644 index 0aa21706160dbc..00000000000000 --- a/.github/workflows/annocheck.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Annocheck - -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - compile: - name: test-annocheck - - runs-on: ubuntu-latest - - container: - image: ghcr.io/ruby/ruby-ci-image:gcc-11 - options: --user root - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - env: - CONFIGURE_TTY: never - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - RUBY_DEBUG: ci rgengc - RUBY_TESTOPTS: >- - -q - --color=always - --tty=no - # FIXME: Drop skipping options - # https://bugs.ruby-lang.org/issues/18061 - # https://sourceware.org/annobin/annobin.html/Test-pie.html - TEST_ANNOCHECK_OPTS: '--skip-pie --skip-gaps' - - steps: - - run: id - working-directory: - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - - # Minimal flags to pass the check. - # -g0 disables backtraces when SEGV. Do not set that. - - name: Run configure - run: > - ../src/configure -C - --enable-debug-env - --disable-install-doc - --with-ext=-test-/cxxanyargs,+ - --without-valgrind - --without-jemalloc - --without-gmp - --with-gcc="gcc-11 -fcf-protection -Wa,--generate-missing-build-notes=yes" - --enable-shared - debugflags=-ggdb3 - optflags=-O2 - LDFLAGS=-Wl,-z,now - - - run: make showflags - - - run: make - - - run: make test-annocheck - - - uses: ./.github/actions/slack - with: - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml deleted file mode 100644 index 207315a084cc59..00000000000000 --- a/.github/workflows/auto_request_review.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Auto Request Review -on: - pull_request_target: - types: [opened, ready_for_review, reopened] - branches: [master] - -permissions: - contents: read - -jobs: - auto-request-review: - name: Auto Request Review - runs-on: ubuntu-latest - if: ${{ github.repository == 'ruby/ruby' && github.base_ref == 'master' }} - steps: - - name: Request review based on files changes and/or groups the author belongs to - uses: necojackarc/auto-request-review@e89da1a8cd7c8c16d9de9c6e763290b6b0e3d424 # v0.13.0 - with: - # scope: public_repo - token: ${{ secrets.MATZBOT_AUTO_REQUEST_REVIEW_TOKEN }} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml deleted file mode 100644 index 4537157b830885..00000000000000 --- a/.github/workflows/baseruby.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: BASERUBY Check - -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - baseruby: - name: BASERUBY - - runs-on: ubuntu-22.04 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - strategy: - matrix: - ruby: - - ruby-3.1 - - ruby-3.2 - - ruby-3.3 - - steps: - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: ${{ matrix.ruby }} - bundler: none - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - uses: ./.github/actions/setup/ubuntu - - - uses: ./.github/actions/setup/directories - with: - makeup: true - - - run: ./configure --disable-install-doc - - - run: make all - - - run: make test - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.ruby }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml deleted file mode 100644 index cb13df330bfb93..00000000000000 --- a/.github/workflows/bundled_gems.yml +++ /dev/null @@ -1,184 +0,0 @@ -name: bundled_gems - -on: - push: - branches: ['master'] - paths: - - '.github/workflows/bundled_gems.yml' - - 'gems/bundled_gems' - pull_request: - branches: ['master'] - paths: - - '.github/workflows/bundled_gems.yml' - - 'gems/bundled_gems' - merge_group: - schedule: - - cron: '45 6 * * *' - workflow_dispatch: - -permissions: # added using https://github.com/step-security/secure-workflows - contents: read - -jobs: - update: - permissions: - contents: write # for Git to git push - - if: ${{ github.event_name != 'schedule' || github.repository == 'ruby/ruby' }} - - name: update ${{ github.workflow }} - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - - uses: ./.github/actions/setup/directories - with: - # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN - checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - - - name: Set ENV - run: | - echo "TODAY=$(date +%F)" >> $GITHUB_ENV - - - name: Download previous gems list - run: | - mkdir -p .downloaded-cache - for data in bundled_gems.json default_gems.json; do - ln -s .downloaded-cache/$data . - curl -O -R -z ./$data https://stdgems.org/$data - done - - - name: Update bundled gems list - id: bundled_gems - run: | - ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT - - - name: Update spec/bundler/support/builders.rb - run: | - #!ruby - rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] - print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} - shell: ruby -i~ {0} spec/bundler/support/builders.rb - - - name: Maintain updated gems list in NEWS - run: | - ruby tool/update-NEWS-gemlist.rb bundled - - - name: Check diffs - id: diff - run: | - news= gems= - git diff --color --no-ext-diff --ignore-submodules --exit-code -- NEWS.md || - news=true - git diff --color --no-ext-diff --ignore-submodules --exit-code -- gems/bundled_gems || - gems=true - git add -- NEWS.md gems/bundled_gems - git add -- spec/bundler/support/builders.rb - echo news=$news >> $GITHUB_OUTPUT - echo gems=$gems >> $GITHUB_OUTPUT - echo update=${news:-$gems} >> $GITHUB_OUTPUT - - - name: Commit - id: commit - run: | - git pull --ff-only origin ${GITHUB_REF#refs/heads/} - message="Update bundled gems list" - if [ -z "${gems}" ]; then - git commit --message="[DOC] ${message} at ${GITHUB_SHA:0:30}" - else - git commit --message="${message} as of ${TODAY}" - fi - env: - TODAY: ${{ steps.bundled_gems.outputs.latest_date || env.TODAY }} - EMAIL: svn-admin@ruby-lang.org - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - gems: ${{ steps.diff.outputs.gems }} - if: ${{ steps.diff.outputs.update }} - - - name: Development revision of bundled gems - run: | - #!ruby - file = "gems/bundled_gems" - - SECONDS_IN_DAY = 86400 - today = Time.new("#{ENV['TODAY']}Z") - if !(december = today.month == 12) - days = 30 - elsif (days = 26 - today.day).positive? - days += 4 - else - puts "::info:: just after released" - exit - end - - since = "#{today.year-1}-12-26" - ref = ENV['GITHUB_REF'] - puts "::group::\e[94mfetching \e[1m#{file}\e[22m since \e[1m#{since}\e[22m from \e[1m#{ref}\e[m" - system(*%W[git fetch --shallow-since=#{since} --no-tags origin #{ref}], exception: true) - puts "::endgroup::" - - puts "\e[94mchecking development version bundled gems older than \e[1m#{days}\e[22m days\e[m" - limit = today.to_i - days * SECONDS_IN_DAY - old = 0 - IO.popen(%W"git blame --line-porcelain -- #{file}") do |blame| - while head = blame.gets("\n\t") and s = blame.gets - next unless (gem = s.split(/\s+|#.*/)).size > 3 - time = head[/^committer-time \K\d+/].to_i - next if (d = limit - time) <= 0 - d /= SECONDS_IN_DAY - line = head[/\A\h+ \d+ \K\d+/].to_i - level = if d < days; 'warning'; else old += 1; 'error'; end - d += days - puts "::#{level} file=#{file},line=#{line},title=Older than #{d} days::#{gem[0]} #{gem[3]}" - end - end - abort "::error title=Too long-standing gems::The release comes soon." if december and old.nonzero? - shell: ruby {0} - env: - file: ${{ steps.logs.outputs.file }} - days: ${{ steps.logs.outputs.days }} - - - name: Install libraries - uses: ./.github/actions/setup/ubuntu - if: ${{ steps.diff.outputs.gems }} - - - name: Build - run: | - ./autogen.sh - ./configure -C --disable-install-doc - make - if: ${{ steps.diff.outputs.gems }} - - - name: Prepare bundled gems - run: | - make -s prepare-gems - if: ${{ steps.diff.outputs.gems }} - - - name: Test bundled gems - run: | - make -s test-bundled-gems - timeout-minutes: 30 - env: - RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - if: ${{ steps.diff.outputs.gems }} - - - name: Push - run: | - git push origin ${GITHUB_REF#refs/heads/} - if: >- - ${{ - github.repository == 'ruby/ruby' && - !startsWith(github.event_name, 'pull') && - steps.commit.outcome == 'success' - }} - - - uses: ./.github/actions/slack - with: - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml deleted file mode 100644 index e8b37d616143b8..00000000000000 --- a/.github/workflows/check_dependencies.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Check Dependencies -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - update-deps: - name: Dependency checks - - strategy: - matrix: - os: [ubuntu-latest] - fail-fast: true - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - uses: ./.github/actions/setup/ubuntu - if: ${{ contains(matrix.os, 'ubuntu') }} - - - uses: ./.github/actions/setup/macos - if: ${{ contains(matrix.os, 'macos') }} - - - uses: ./.github/actions/setup/directories - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - - - name: Run configure - run: ./configure -C --disable-install-doc --disable-rubygems --with-gcc 'optflags=-O0' 'debugflags=-save-temps=obj -g' - - - run: make fix-depends - - - run: git diff --no-ext-diff --ignore-submodules --exit-code - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.os }} / Dependencies need to update - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml deleted file mode 100644 index d4a2c6f3fef6eb..00000000000000 --- a/.github/workflows/check_misc.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Misc -on: [push, pull_request, merge_group] - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - checks: - name: Miscellaneous checks - - permissions: - contents: write # for Git to git push - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - - uses: ./.github/actions/setup/directories - with: - makeup: true - # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN - checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - - # Run this step first to make sure auto-style commits are pushed - - name: ${{ github.ref == 'refs/heads/master' && 'Auto-correct' || 'Check for' }} code styles - run: | - set -x - ruby tool/auto-style.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$PUSH_REF" - env: - EMAIL: svn-admin@ruby-lang.org - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - GITHUB_OLD_SHA: ${{ github.event.pull_request.base.sha }} - GITHUB_NEW_SHA: ${{ github.event.pull_request.merge_commit_sha }} - PUSH_REF: ${{ github.ref == 'refs/heads/master' && github.ref || '' }} - if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - - - name: Check if C-sources are US-ASCII - run: | - grep -r -n --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : - - - name: Check for bash specific substitution in configure.ac - run: | - git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac && exit 1 || : - - - name: Check for header macros - run: | - fail= - for header in ruby/*.h; do - git grep -l -F -e $header -e HAVE_`echo $header | tr a-z./ A-Z__` -- . > /dev/null && continue - fail=1 - echo $header - done - exit $fail - working-directory: include - - - name: Check if to generate documents - id: rdoc - run: | - ref=$(sed 's/#.*//;/^rdoc /!d' gems/bundled_gems | awk '{print $4}') - echo ref=$ref >> $GITHUB_OUTPUT - # Generate only when document commit/PR - if: >- - ${{false - || contains(github.event.head_commit.message, '[ruby/rdoc]') - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - }} - - - name: Checkout rdoc - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - repository: ruby/rdoc - ref: ${{ steps.rdoc.outputs.ref }} - path: .bundle/gems/rdoc-0 - if: ${{ steps.rdoc.outputs.ref != '' }} - - - name: Generate rdoc - run: | - set -x - gempath=$(ruby -e 'print Gem.user_dir, "/bin"') - PATH=$gempath:$PATH - gem install --user bundler - bundle config --local path vendor/bundle - bundle install --jobs 4 - bundle exec rake generate - working-directory: .bundle/gems/rdoc-0 - if: ${{ steps.rdoc.outputs.ref != '' }} - - - name: Generate docs - id: docs - run: | - $RDOC -C -x ^ext -x ^lib . - $RDOC --op html . - echo htmlout=ruby-html-${GITHUB_SHA:0:10} >> $GITHUB_OUTPUT - env: - RDOC: ruby -W0 --disable-gems tool/rdoc-srcdir -q - if: ${{ steps.rdoc.outcome == 'success' }} - - - name: Upload docs - uses: actions/upload-artifact@v4 - with: - path: html - name: ${{ steps.docs.outputs.htmlout }} - if: ${{ steps.docs.outcome == 'success' }} - - - uses: ./.github/actions/slack - with: - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 0b4103913bcae5..00000000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: 'CodeQL' - -on: - push: - branches: ['master'] - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - schedule: - - cron: '0 12 * * *' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: # added using https://github.com/step-security/secure-workflows - contents: read - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read # for github/codeql-action/init to get workflow details - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/autobuild to send a status report - # CodeQL fails to run pull requests from dependabot due to missing write access to upload results. - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - env: - enable_install_doc: no - - strategy: - fail-fast: false - matrix: - include: - - language: cpp - - language: ruby - - steps: - - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Install libraries - if: ${{ contains(matrix.os, 'macos') }} - uses: ./.github/actions/setup/macos - - - name: Install libraries - if : ${{ matrix.os == 'ubuntu-latest' }} - uses: ./.github/actions/setup/ubuntu - - - uses: ./.github/actions/setup/directories - - - name: Remove an obsolete rubygems vendored file - if: ${{ matrix.os == 'ubuntu-latest' }} - run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - - - name: Initialize CodeQL - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 - with: - languages: ${{ matrix.language }} - trap-caching: false - debug: true - - - name: Autobuild - uses: github/codeql-action/autobuild@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 - with: - category: '/language:${{ matrix.language }}' - upload: False - output: sarif-results - - - name: filter-sarif - uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0.1 - with: - patterns: | - +**/*.rb - -lib/uri/mailto.rb:rb/overly-large-range - -lib/uri/rfc3986_parser.rb:rb/overly-large-range - -lib/bundler/vendor/uri/lib/uri/mailto.rb:rb/overly-large-range - -lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb:rb/overly-large-range - -test/ruby/test_io.rb:rb/non-constant-kernel-open - -test/open-uri/test_open-uri.rb:rb/non-constant-kernel-open - -test/open-uri/test_ssl.rb:rb/non-constant-kernel-open - -spec/ruby/core/io/binread_spec.rb:rb/non-constant-kernel-open - -spec/ruby/core/io/readlines_spec.rb:rb/non-constant-kernel-open - -spec/ruby/core/io/foreach_spec.rb:rb/non-constant-kernel-open - -spec/ruby/core/io/write_spec.rb:rb/non-constant-kernel-open - -spec/ruby/core/io/read_spec.rb:rb/non-constant-kernel-open - -spec/ruby/core/kernel/open_spec.rb:rb/non-constant-kernel-open - input: sarif-results/${{ matrix.language }}.sarif - output: sarif-results/${{ matrix.language }}.sarif - if: ${{ matrix.language == 'ruby' }} - continue-on-error: true - - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 - with: - sarif_file: sarif-results/${{ matrix.language }}.sarif - continue-on-error: true diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml deleted file mode 100644 index c53be6f410c5d1..00000000000000 --- a/.github/workflows/compilers.yml +++ /dev/null @@ -1,333 +0,0 @@ -# Some tests depending on this name 'Compilations' via $GITHUB_WORKFLOW. Make sure to update such tests when renaming this workflow. -name: Compilations - -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -# Each job is split so that they roughly take 30min to run through. -jobs: - compile-if: - name: 'omnibus compilations, trigger' - runs-on: ubuntu-latest - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - steps: - - run: true - working-directory: - - compile1: - name: 'omnibus compilations, #1' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - # Set fetch-depth: 10 so that Launchable can receive commits information. - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - name: 'clang 18 LTO' - uses: './.github/actions/compilers' - with: - tag: clang-18 - with_gcc: 'clang-18 -flto=auto' - optflags: '-O2' - enable_shared: false - - { uses: './.github/actions/compilers', name: '-O0', with: { optflags: '-O0 -march=x86-64 -mtune=generic' } } - # - { uses: './.github/actions/compilers', name: '-O3', with: { optflags: '-O3 -march=x86-64 -mtune=generic', check: true } } - - compile2: - name: 'omnibus compilations, #2' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - name: 'GCC 15 LTO' - uses: './.github/actions/compilers' - with: - tag: gcc-15 - with_gcc: 'gcc-15 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' - optflags: '-O2' - enable_shared: false - - { uses: './.github/actions/compilers', name: 'ext/Setup', with: { static_exts: 'etc json/* */escape' } } - - { uses: './.github/actions/compilers', name: 'GCC 15', with: { tag: 'gcc-15' } } - - { uses: './.github/actions/compilers', name: 'GCC 14', with: { tag: 'gcc-14' } } - - { uses: './.github/actions/compilers', name: 'GCC 13', with: { tag: 'gcc-13' } } - - { uses: './.github/actions/compilers', name: 'GCC 12', with: { tag: 'gcc-12' } } - - { uses: './.github/actions/compilers', name: 'GCC 11', with: { tag: 'gcc-11' } } - - { uses: './.github/actions/compilers', name: 'GCC 10', with: { tag: 'gcc-10' } } - - { uses: './.github/actions/compilers', name: 'GCC 9', with: { tag: 'gcc-9' } } - - { uses: './.github/actions/compilers', name: 'GCC 8', with: { tag: 'gcc-8' } } - - { uses: './.github/actions/compilers', name: 'GCC 7', with: { tag: 'gcc-7' } } - - compile3: - name: 'omnibus compilations, #3' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'clang 22', with: { tag: 'clang-22' } } - - { uses: './.github/actions/compilers', name: 'clang 21', with: { tag: 'clang-21' } } - - { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' } } - - { uses: './.github/actions/compilers', name: 'clang 19', with: { tag: 'clang-19' } } - - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' } } - - { uses: './.github/actions/compilers', name: 'clang 17', with: { tag: 'clang-17' } } - - { uses: './.github/actions/compilers', name: 'clang 16', with: { tag: 'clang-16' } } - - { uses: './.github/actions/compilers', name: 'clang 15', with: { tag: 'clang-15' } } - - { uses: './.github/actions/compilers', name: 'clang 14', with: { tag: 'clang-14' } } - - compile4: - name: 'omnibus compilations, #4' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'clang 13', with: { tag: 'clang-13' } } - - { uses: './.github/actions/compilers', name: 'clang 12', with: { tag: 'clang-12' } } - - { uses: './.github/actions/compilers', name: 'clang 11', with: { tag: 'clang-11' } } - - { uses: './.github/actions/compilers', name: 'clang 10', with: { tag: 'clang-10' } } - # llvm-objcopy<=9 doesn't have --wildcard. It compiles, but leaves Rust symbols in libyjit.o. - - { uses: './.github/actions/compilers', name: 'clang 9', with: { tag: 'clang-9', append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'clang 8', with: { tag: 'clang-8', append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'clang 7', with: { tag: 'clang-7', append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'clang 6', with: { tag: 'clang-6.0', append_configure: '--disable-yjit' } } - - compile5: - name: 'omnibus compilations, #5' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - # -Wno-strict-prototypes is necessary with current clang-15 since - # older autoconf generate functions without prototype and -pedantic - # now implies strict-prototypes. Disabling the error but leaving the - # warning generates a lot of noise from use of ANYARGS in - # rb_define_method() and friends. - # See: https://github.com/llvm/llvm-project/commit/11da1b53d8cd3507959022cd790d5a7ad4573d94 - - { uses: './.github/actions/compilers', name: 'C99', with: { CFLAGS: '-std=c99 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C11', with: { CFLAGS: '-std=c11 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C17', with: { CFLAGS: '-std=c17 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C23', with: { CFLAGS: '-std=c2x -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C++98', with: { CXXFLAGS: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++11', with: { CXXFLAGS: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++14', with: { CXXFLAGS: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++17', with: { CXXFLAGS: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - compile6: - name: 'omnibus compilations, #6' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++23', with: { CXXFLAGS: '-std=c++23 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++26', with: { CXXFLAGS: '-std=c++26 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'gmp', with: { append_configure: '--with-gmp', check: 'ruby/test_bignum.rb', mspecopt: "/github/workspace/src/spec/ruby/core/integer" } } - - { uses: './.github/actions/compilers', name: 'jemalloc', with: { append_configure: '--with-jemalloc' } } - - { uses: './.github/actions/compilers', name: 'valgrind', with: { append_configure: '--with-valgrind' } } - - { uses: './.github/actions/compilers', name: 'coroutine=ucontext', with: { append_configure: '--with-coroutine=ucontext' } } - - { uses: './.github/actions/compilers', name: 'coroutine=pthread', with: { append_configure: '--with-coroutine=pthread' } } - - compile7: - name: 'omnibus compilations, #7' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'disable-jit', with: { append_configure: '--disable-yjit --disable-zjit' } } - - { uses: './.github/actions/compilers', name: 'disable-yjit', with: { append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'disable-zjit', with: { append_configure: '--disable-zjit' } } - - { uses: './.github/actions/compilers', name: 'disable-dln', with: { append_configure: '--disable-dln' } } - - { uses: './.github/actions/compilers', name: 'enable-mkmf-verbose', with: { append_configure: '--enable-mkmf-verbose' } } - - { uses: './.github/actions/compilers', name: 'disable-rubygems', with: { append_configure: '--disable-rubygems' } } - - { uses: './.github/actions/compilers', name: 'RUBY_DEVEL', with: { append_configure: '--enable-devel' } } - - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=0', with: { cppflags: '-DOPT_THREADED_CODE=0' } } - - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=1', with: { cppflags: '-DOPT_THREADED_CODE=1' } } - - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=2', with: { cppflags: '-DOPT_THREADED_CODE=2' } } - - compile8: - name: 'omnibus compilations, #8' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'NDEBUG', with: { cppflags: '-DNDEBUG' } } - - { uses: './.github/actions/compilers', name: 'RUBY_DEBUG', with: { cppflags: '-DRUBY_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'ARRAY_DEBUG', with: { cppflags: '-DARRAY_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'BIGNUM_DEBUG', with: { cppflags: '-DBIGNUM_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'CCAN_LIST_DEBUG', with: { cppflags: '-DCCAN_LIST_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'CPDEBUG=-1', with: { cppflags: '-DCPDEBUG=-1' } } - - { uses: './.github/actions/compilers', name: 'ENC_DEBUG', with: { cppflags: '-DENC_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'GC_DEBUG', with: { cppflags: '-DGC_DEBUG' } } - - compile9: - name: 'omnibus compilations, #9' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'HASH_DEBUG', with: { cppflags: '-DHASH_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'ID_TABLE_DEBUG', with: { cppflags: '-DID_TABLE_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_DEBUG=-1', with: { cppflags: '-DRGENGC_DEBUG=-1' } } - - { uses: './.github/actions/compilers', name: 'SYMBOL_DEBUG', with: { cppflags: '-DSYMBOL_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' } } - - { uses: './.github/actions/compilers', name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' } } - - { uses: './.github/actions/compilers', name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' } } - - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit' } } - - compileX: - name: 'omnibus compilations, #10' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'USE_LAZY_LOAD', with: { cppflags: '-DUSE_LAZY_LOAD' } } - - { uses: './.github/actions/compilers', name: 'USE_SYMBOL_GC=0', with: { cppflags: '-DUSE_SYMBOL_GC=0' } } - - { uses: './.github/actions/compilers', name: 'USE_THREAD_CACHE=0', with: { cppflags: '-DUSE_THREAD_CACHE=0' } } - - { uses: './.github/actions/compilers', name: 'USE_RUBY_DEBUG_LOG=1', with: { cppflags: '-DUSE_RUBY_DEBUG_LOG=1' } } - - { uses: './.github/actions/compilers', name: 'USE_DEBUG_COUNTER', with: { cppflags: '-DUSE_DEBUG_COUNTER=1' } } - - { uses: './.github/actions/compilers', name: 'SHARABLE_MIDDLE_SUBSTRING', with: { cppflags: '-DSHARABLE_MIDDLE_SUBSTRING=1' } } - - { uses: './.github/actions/compilers', name: 'DEBUG_FIND_TIME_NUMGUESS', with: { cppflags: '-DDEBUG_FIND_TIME_NUMGUESS' } } - - { uses: './.github/actions/compilers', name: 'DEBUG_INTEGER_PACK', with: { cppflags: '-DDEBUG_INTEGER_PACK' } } - - compileB: - name: 'omnibus compilations, #11' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' } } - - { uses: './.github/actions/compilers', name: 'GC_ENABLE_LAZY_SWEEP=0', with: { cppflags: '-DGC_ENABLE_LAZY_SWEEP=0' } } - - { uses: './.github/actions/compilers', name: 'GC_PROFILE_DETAIL_MEMORY', with: { cppflags: '-DGC_PROFILE_DETAIL_MEMORY' } } - - { uses: './.github/actions/compilers', name: 'GC_PROFILE_MORE_DETAIL', with: { cppflags: '-DGC_PROFILE_MORE_DETAIL' } } - - { uses: './.github/actions/compilers', name: 'MALLOC_ALLOCATED_SIZE_CHECK', with: { cppflags: '-DMALLOC_ALLOCATED_SIZE_CHECK' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_ESTIMATE_OLDMALLOC', with: { cppflags: '-DRGENGC_ESTIMATE_OLDMALLOC' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_PROFILE', with: { cppflags: '-DRGENGC_PROFILE' } } - - compileC: - name: 'omnibus compilations, #12' - runs-on: ubuntu-latest - needs: compile-if - if: ${{ needs.compile-if.result == 'success' }} - timeout-minutes: 60 - services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' } } - - { uses: './.github/actions/compilers', name: 'VM_DEBUG_VERIFY_METHOD_CACHE', with: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } } - - { uses: './.github/actions/compilers', name: 'YJIT_FORCE_ENABLE', with: { cppflags: '-DYJIT_FORCE_ENABLE' } } - - { uses: './.github/actions/compilers', name: 'UNIVERSAL_PARSER', with: { cppflags: '-DUNIVERSAL_PARSER' } } - - compilemax: - name: 'omnibus compilations, result' - runs-on: ubuntu-latest - if: ${{ always() }} - needs: - - 'compile1' - - 'compile2' - - 'compile3' - - 'compile4' - - 'compile5' - - 'compile6' - - 'compile7' - - 'compile8' - - 'compile9' - - 'compileX' - - 'compileB' - - 'compileC' - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - - uses: ./.github/actions/slack - with: - label: 'omnibus' - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} - - run: false - working-directory: - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml deleted file mode 100644 index 39a98cd30ec9d1..00000000000000 --- a/.github/workflows/cygwin.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Cygwin -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - runs-on: windows-2022 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - run: git config --global core.autocrlf input - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Setup Cygwin - uses: cygwin/cygwin-install-action@master - with: - packages: ruby gcc-core make autoconf libtool libssl-devel libyaml-devel libffi-devel zlib-devel - - - name: configure - run: | - ./autogen.sh - ./configure --disable-install-doc - shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} - - - name: Extract bundled gems - run: | - make ruby -j5 - make extract-gems - shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} - - - name: make all - timeout-minutes: 30 - run: make -j4 V=1 - shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} diff --git a/.github/workflows/default_gems.yml b/.github/workflows/default_gems.yml deleted file mode 100644 index 7935afc44a333c..00000000000000 --- a/.github/workflows/default_gems.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Update default gems list -on: [push, pull_request, merge_group] - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - update_default_gems: - name: Update default gems list - - permissions: - contents: write # for Git to git push - - runs-on: ubuntu-latest - - if: ${{ github.repository == 'ruby/ruby' }} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - - id: gems - run: true - if: ${{ github.ref == 'refs/heads/master' }} - - - uses: ./.github/actions/setup/directories - with: - makeup: true - # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN - checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - if: ${{ steps.gems.outcome == 'success' }} - - - name: Download previous gems list - run: | - data=default_gems.json - mkdir -p .downloaded-cache - ln -s .downloaded-cache/$data . - curl -O -R -z ./$data https://stdgems.org/$data - if: ${{ steps.gems.outcome == 'success' }} - - - name: Make default gems list - run: | - #!ruby - require 'rubygems' - $:.unshift "lib" - rgver = File.foreach("lib/rubygems.rb") do |line| - break $1 if /^\s*VERSION\s*=\s*"([^"]+)"/ =~ line - end - gems = Dir.glob("{ext,lib}/**/*.gemspec").map do |f| - spec = Gem::Specification.load(f) - "#{spec.name} #{spec.version}" - end.sort - File.open("gems/default_gems", "w") do |f| - f.puts "RubyGems #{rgver}" - f.puts gems - end - shell: ruby --disable=gems {0} - if: ${{ steps.gems.outcome == 'success' }} - - - name: Maintain updated gems list in NEWS - run: | - ruby tool/update-NEWS-gemlist.rb default - if: ${{ steps.gems.outcome == 'success' }} - - - name: Check diffs - id: diff - run: | - git diff --color --no-ext-diff --ignore-submodules --exit-code NEWS.md || - echo update=true >> $GITHUB_OUTPUT - if: ${{ steps.gems.outcome == 'success' }} - - - name: Commit - run: | - git pull --ff-only origin ${GITHUB_REF#refs/heads/} - git commit --message="Update default gems list at ${GITHUB_SHA:0:30} [ci skip]" NEWS.md - git push origin ${GITHUB_REF#refs/heads/} - env: - EMAIL: svn-admin@ruby-lang.org - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - if: >- - ${{ - github.repository == 'ruby/ruby' && - !startsWith(github.event_name, 'pull') && - steps.diff.outputs.update - }} - - - uses: ./.github/actions/slack - with: - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml deleted file mode 100644 index 3a2ba704aec7ec..00000000000000 --- a/.github/workflows/dependabot_automerge.yml +++ /dev/null @@ -1,32 +0,0 @@ -# from https://github.com/gofiber/swagger/blob/main/.github/workflows/dependabot_automerge.yml -name: Dependabot auto-merge -on: - pull_request: - -permissions: - contents: write - pull-requests: write - -jobs: - automerge: - runs-on: ubuntu-latest - if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'ruby/ruby' - steps: - - name: Dependabot metadata - uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b # v2.4.0 - id: metadata - - - name: Wait for status checks - uses: lewagon/wait-on-check-action@0dceb95e7c4cad8cc7422aee3885998f5cab9c79 # v1.4.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.event.pull_request.head.sha || github.sha }} - check-regexp: 'make \(check, .*\)' - wait-interval: 30 - - - name: Auto-merge for Dependabot PRs - if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch' }} - run: gh pr merge --auto --rebase "$PR_URL" - env: - PR_URL: ${{ github.event.pull_request.html_url }} - GITHUB_TOKEN: ${{ secrets.MATZBOT_DEPENDABOT_MERGE_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index e57cd86e2b3c7f..00000000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: "Pull Request Labeler" -on: -- pull_request_target - -jobs: - labeler: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v5 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 70b2bc9d68d109..00000000000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,205 +0,0 @@ -name: macOS -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - # Do not use paths-ignore for required status checks - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - strategy: - matrix: - include: - - test_task: check - os: macos-14 - - test_task: check - os: macos-14 - configure_args: '--with-gcc=gcc-14' - - test_task: check - os: macos-14 - configure_args: '--with-jemalloc --with-opt-dir=$(brew --prefix jemalloc)' - - test_task: check - os: macos-14 - configure_args: '--with-gmp' - - test_task: test-all - test_opts: --repeat-count=2 - os: macos-14 - - test_task: test-bundler-parallel - os: macos-14 - - test_task: test-bundled-gems - os: macos-14 - - test_task: check - os: macos-15 - extra_checks: [capi] - - test_task: check - os: macos-13 - fail-fast: false - - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - - runs-on: ${{ matrix.os }} - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - name: Install libraries - uses: ./.github/actions/setup/macos - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - clean: true - dummy-files: ${{ matrix.test_task == 'check' }} - # Set fetch-depth: 0 so that Launchable can receive commits information. - fetch-depth: 10 - - - name: make sure that kern.coredump=1 - run: | - sysctl -n kern.coredump - sudo sysctl -w kern.coredump=1 - sudo chmod -R +rwx /cores/ - - - name: Delete unused SDKs - # To free up disk space to not run out during the run - run: | - sudo rm -rf ~/.dotnet - sudo rm -rf /Library/Android - sudo rm -rf /Library/Developer/CoreSimulator - continue-on-error: true - - - name: Run configure - run: ../src/configure -C --disable-install-doc ${ruby_configure_args} ${{ matrix.configure_args }} - - - run: make prepare-gems - if: ${{ matrix.test_task == 'test-bundled-gems' }} - - - run: make - - - run: make hello - - - name: runirb - run: | - echo IRB::VERSION | make runirb RUNOPT="-- -f" - - - name: Set test options for skipped tests - run: | - set -x - TESTS="$(echo "${{ matrix.skipped_tests }}" | sed 's| |$$/ -n!/|g;s|^|-n!/|;s|$|$$/|')" - echo "TESTS=${TESTS}" >> $GITHUB_ENV - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - - - name: Set up Launchable - id: launchable - uses: ./.github/actions/launchable/setup - with: - os: ${{ matrix.os }} - test-opts: ${{ matrix.test_opts }} - launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} - builddir: build - srcdir: src - continue-on-error: true - timeout-minutes: 3 - - - name: Set extra test options - run: | - echo "TESTS=$TESTS ${{ matrix.test_opts }}" >> $GITHUB_ENV - echo "RUBY_TEST_TIMEOUT_SCALE=10" >> $GITHUB_ENV # With --repeat-count=2, flaky test by timeout occurs frequently for some reason - if: matrix.test_opts - - - name: make ${{ matrix.test_task }} - run: | - test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") - test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") - - ulimit -c unlimited - make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} - timeout-minutes: 90 - env: - RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - PRECHECK_BUNDLED_GEMS: 'no' - LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} - LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - - - name: make skipped tests - run: | - make -s test-all TESTS="${TESTS//-n!\//-n/}" - env: - GNUMAKEFLAGS: '' - RUBY_TESTOPTS: '-v --tty=no' - PRECHECK_BUNDLED_GEMS: 'no' - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - - name: CAPI extensions - uses: ./.github/actions/capiext - with: - builddir: build - env: - RUBY_TESTOPTS: '-v --tty=no' - if: ${{ contains(matrix.extra_checks, 'capi') }} - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.os }} / ${{ matrix.test_task }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - - - name: Resolve job ID - id: job_id - uses: actions/github-script@main - env: - matrix: ${{ toJson(matrix) }} - with: - script: | - const { data: workflow_run } = await github.rest.actions.listJobsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.runId - }); - const matrix = JSON.parse(process.env.matrix); - const job_name = `${context.job}${matrix ? ` (${Object.values(matrix).join(", ")})` : ""}`; - return workflow_run.jobs.find((job) => job.name === job_name).id; - - result: - if: ${{ always() }} - name: ${{ github.workflow }} result - runs-on: macos-latest - needs: [make] - steps: - - run: exit 1 - working-directory: - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml deleted file mode 100644 index 4a71f1753011a6..00000000000000 --- a/.github/workflows/mingw.yml +++ /dev/null @@ -1,221 +0,0 @@ -name: MinGW -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -# Notes: -# Actions console encoding causes issues, see test-all & test-spec steps -# -jobs: - make: - runs-on: windows-2022 - - name: ${{ github.workflow }} (${{ matrix.msystem }}) - - env: - MSYSTEM: ${{ matrix.msystem }} - MSYS2_ARCH: x86_64 - CHOST: 'x86_64-w64-mingw32' - CFLAGS: '-march=x86-64 -mtune=generic -O3 -pipe' - CXXFLAGS: '-march=x86-64 -mtune=generic -O3 -pipe' - CPPFLAGS: '-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048' - LDFLAGS: '-pipe' - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - - strategy: - matrix: - include: - # To mitigate flakiness of MinGW CI, we test only one runtime that newer MSYS2 uses. - # Ruby 3.2 is the first Windows Ruby to use OpenSSL 3.x - - msystem: 'UCRT64' - test_task: 'check' - test-all-opts: '--name=!/TestObjSpace#test_reachable_objects_during_iteration/' - fail-fast: false - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: msys2/setup-msys2@fb197b72ce45fb24f17bf3f807a388985654d1f2 # v2.29.0 - id: msys2 - with: - msystem: UCRT64 - update: true - install: >- - git - make - ruby - autoconf - mingw-w64-ucrt-x86_64-gcc - mingw-w64-ucrt-x86_64-ragel - mingw-w64-ucrt-x86_64-openssl - mingw-w64-ucrt-x86_64-libyaml - mingw-w64-ucrt-x86_64-libffi - - - name: Set up env - id: setup-env - working-directory: - run: | - $msys2 = ${env:MSYS2_LOCATION} - echo $msys2\usr\bin $msys2\ucrt64\bin | - Tee-Object ${env:GITHUB_PATH} -Append -Encoding utf-8 - - # Use the fast device for the temporary directory. - # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. - # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 - $tmp = ${env:RUNNER_TEMP} - echo HOME=$home TMP=$tmp TEMP=$tmp TMPDIR=$tmp | - Tee-Object ${env:GITHUB_ENV} -Append -Encoding utf-8 - shell: pwsh # cmd.exe does not strip spaces before `|`. - env: - MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }} - - - name: Remove Strawberry Perl pkg-config - working-directory: - # `pkg-config.bat` included in Strawberry Perl is written in - # Perl and doesn't work when another msys2 `perl` precede its - # own `perl`. - # - # ``` - # Can't find C:\Strawberry\perl\bin\pkg-config.bat on PATH, '.' not in PATH. - # ``` - run: | - Get-Command pkg-config.bat | % { ren $_.path ($_.path + "~") } - shell: pwsh - - - name: Misc system & package info - working-directory: - run: | - group() { echo ::group::$'\e[94;1m'"$*"$'\e[m'; } - endgroup() { echo ::endgroup::; } - - group Path - cygpath -wa / . $(type -p cygpath bash sh) - endgroup - - I() { - group $1 - run Where type -pa $1 && { [ $# -eq 1 ] || run Version "$@"; } || - failed+=($1) - endgroup - } - run() { local w m=$1; shift; w="$("$@")" && show "$m" && indent "$w"; } - indent() { [ -z "$1" ] || echo "$1" | /bin/sed '/^$/!s/^/ /'; } - show() { echo $'\e[96m'"$*"$'\e[m'; } - - failed=() - - I gcc.exe --version - I ragel.exe --version - I make.exe --version - I openssl.exe version - I libcrypto-3-x64.dll - I libssl-3-x64.dll - - group Packages - pacman -Qs mingw-w64-ucrt-x86_64-* | /bin/sed -n "s,local/mingw-w64-ucrt-x86_64-,,p" - endgroup - - [ ${#failed[@]} -eq 0 ] - shell: sh - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - # Set fetch-depth: 10 so that Launchable can receive commits information. - fetch-depth: 10 - - - name: configure - run: > - ../src/configure --disable-install-doc --prefix=/. - --build=$CHOST --host=$CHOST --target=$CHOST - shell: sh - - - name: make all - timeout-minutes: 30 - run: make -j4 - - - name: make install - run: make DESTDIR=../install install-nodoc - - - name: Set up Launchable - uses: ./.github/actions/launchable/setup - with: - os: windows-2022 - launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} - builddir: build - srcdir: src - test-tasks: '["test", "test-all", "test-spec"]' - continue-on-error: true - timeout-minutes: 3 - - - name: test - timeout-minutes: 30 - run: make test - env: - GNUMAKEFLAGS: '' - RUBY_TESTOPTS: '-v --tty=no' - if: ${{ matrix.test_task == 'check' || matrix.test_task == 'test' }} - - - name: test-all - timeout-minutes: 45 - run: | - make ${{ StartsWith(matrix.test_task, 'test/') && matrix.test_task || 'test-all' }} - env: - RUBY_TESTOPTS: >- - --retry --job-status=normal --show-skip --timeout-scale=1.5 -j4 - ${{ matrix.test-all-opts }} - ${{ env.TESTS }} - BUNDLER_VERSION: - if: ${{ matrix.test_task == 'check' || matrix.test_task == 'test-all' || StartsWith(matrix.test_task, 'test/') }} - - - name: test-spec - timeout-minutes: 10 - run: | - make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} - if: ${{ matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/') }} - - - uses: ./src/.github/actions/slack - with: - label: ${{ matrix.msystem }} / ${{ matrix.test_task }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - -defaults: - run: - working-directory: build - shell: cmd diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml deleted file mode 100644 index 96b1afa05fc48b..00000000000000 --- a/.github/workflows/modgc.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: ModGC -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - # Do not use paths-ignore for required status checks - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - check: - strategy: - matrix: - gc: - - name: default - - name: mmtk - mmtk_build: release - os: [macos-latest, ubuntu-latest] - include: - - test_task: check - fail-fast: false - - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - RUBY_DEBUG: ci - - runs-on: ${{ matrix.os }} - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - name: Install libraries (macOS) - uses: ./.github/actions/setup/macos - if: ${{ contains(matrix.os, 'macos') }} - - - name: Install libraries (Ubuntu) - uses: ./.github/actions/setup/ubuntu - if: ${{ contains(matrix.os, 'ubuntu') }} - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - if: ${{ contains(matrix.os, 'ubuntu') }} - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - clean: true - dummy-files: false - # Set fetch-depth: 10 so that Launchable can receive commits information. - fetch-depth: 10 - - - name: make sure that kern.coredump=1 - run: | - sysctl -n kern.coredump - sudo sysctl -w kern.coredump=1 - sudo chmod -R +rwx /cores/ - if: ${{ contains(matrix.os, 'macos') }} - - - name: Delete unused SDKs - # To free up disk space to not run out during the run - run: | - sudo rm -rf ~/.dotnet - sudo rm -rf /Library/Android - sudo rm -rf /Library/Developer/CoreSimulator - continue-on-error: true - if: ${{ contains(matrix.os, 'macos') }} - - - name: Setup Ruby GC Directory - run: | - echo "MODULAR_GC_DIR=$HOME/ruby_gc" >> $GITHUB_ENV - - - name: Run configure - env: - arch: ${{ matrix.arch }} - run: >- - $SETARCH ../src/configure -C --disable-install-doc --with-modular-gc=${{ env.MODULAR_GC_DIR }} - ${arch:+--target=$arch-$OSTYPE --host=$arch-$OSTYPE} - - - uses: actions-rust-lang/setup-rust-toolchain@v1 - - name: Set MMTk environment variables - run: | - echo 'EXCLUDES=../src/test/.excludes-mmtk' >> $GITHUB_ENV - echo 'MSPECOPT=-B../src/spec/mmtk.mspec' >> $GITHUB_ENV - if: ${{ matrix.gc.name == 'mmtk' }} - - - run: $SETARCH make - - - name: Build Modular GC - run: | - echo "RUBY_GC_LIBRARY=${{ matrix.gc.name }}" >> $GITHUB_ENV - make install-modular-gc MODULAR_GC=${{ matrix.gc.name }} MMTK_BUILD=${{ matrix.gc.mmtk_build }} - make distclean-modular-gc MODULAR_GC=${{ matrix.gc.name }} - - - run: $SETARCH make hello - - - name: Set test options for skipped tests - run: | - set -x - TESTS="$(echo "${{ matrix.skipped_tests }}" | sed 's| |$$/ -n!/|g;s|^|-n!/|;s|$|$$/|')" - echo "TESTS=${TESTS}" >> $GITHUB_ENV - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - - - name: Set up Launchable - id: launchable - uses: ./.github/actions/launchable/setup - with: - os: ${{ matrix.os || 'ubuntu-22.04' }} - test-opts: ${{ matrix.configure }} - launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} - builddir: build - srcdir: src - continue-on-error: true - timeout-minutes: 3 - - - name: make ${{ matrix.test_task }} - run: | - test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") - test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") - - $SETARCH make -s ${{ matrix.test_task }} \ - ${TESTS:+TESTS="$TESTS"} \ - ${{ !contains(matrix.test_task, 'bundle') && 'RUBYOPT=-w' || '' }} - timeout-minutes: ${{ matrix.gc.timeout || 40 }} - env: - RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - PRECHECK_BUNDLED_GEMS: 'no' - LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} - LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - - - name: make skipped tests - run: | - $SETARCH make -s test-all TESTS="${TESTS//-n!\//-n/}" - env: - GNUMAKEFLAGS: '' - RUBY_TESTOPTS: '-v --tty=no' - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml deleted file mode 100644 index 04be9f309d1c33..00000000000000 --- a/.github/workflows/parse_y.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: parse.y -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - strategy: - matrix: - include: - - test_task: check - - test_task: test-bundler-parallel - - test_task: test-bundled-gems - fail-fast: false - - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - RUBY_DEBUG: ci - SETARCH: ${{ matrix.arch && format('setarch {0}', matrix.arch) }} - - runs-on: ubuntu-22.04 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/ubuntu - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - clean: true - dummy-files: ${{ matrix.test_task == 'check' }} - - - name: Run configure - run: ../src/configure -C --disable-install-doc cppflags=-DRUBY_DEBUG --with-parser=parse.y - - - run: make - - - run: make TESTRUN_SCRIPT='-renvutil -v -e "exit EnvUtil.current_parser == %[parse.y]"' run - env: - RUNOPT0: -I$(tooldir)/lib - - - name: make ${{ matrix.test_task }} - run: make -s ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" SPECOPTS="$SPECOPTS" - env: - RUBY_TESTOPTS: ${{ matrix.testopts }} - EXCLUDES: '../src/test/.excludes-parsey' - RUN_OPTS: ${{ matrix.run_opts || '--parser=parse.y' }} - SPECOPTS: ${{ matrix.specopts || '-T --parser=parse.y' }} - TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.run_opts }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/pr-playground.yml b/.github/workflows/pr-playground.yml deleted file mode 100644 index f3c05564294778..00000000000000 --- a/.github/workflows/pr-playground.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Post Playground link to PR -on: - pull_request_target: - types: [labeled] - workflow_run: - workflows: ["WebAssembly"] - types: [completed] - -jobs: - post-summary: - name: Post Playground link - runs-on: ubuntu-latest - permissions: - pull-requests: write - # Post a comment only if the PR status check is passed and the PR is labeled with `Playground`. - # Triggered twice: when the PR is labeled and when PR build is passed. - if: >- - ${{ false - || (true - && github.event_name == 'pull_request_target' - && contains(github.event.pull_request.labels.*.name, 'Playground')) - || (true - && github.event_name == 'workflow_run' - && github.event.workflow_run.conclusion == 'success' - && github.event.workflow_run.event == 'pull_request') - }} - steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs/promises'); - - const buildWorkflowPath = '.github/workflows/wasm.yml'; - const findSuccessfuBuildRun = async (pr) => { - const opts = github.rest.actions.listWorkflowRunsForRepo.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - status: 'success', - branch: pr.head.ref, - }); - const runs = await github.paginate(opts); - const buildRun = runs.find(run => run.path == buildWorkflowPath); - return buildRun; - } - - const postComment = async (body, pr) => { - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - }); - - const commentOpts = { owner: context.repo.owner, repo: context.repo.repo, body: comment }; - - const existingComment = comments.find(comment => comment.body.startsWith(magicComment)); - if (existingComment) { - core.info(`Updating existing comment: ${existingComment.html_url}`); - await github.rest.issues.updateComment({ - ...commentOpts, comment_id: existingComment.id - }); - } else { - await github.rest.issues.createComment({ - ...commentOpts, issue_number: pr.number - }); - } - } - - const derivePRNumber = async () => { - if (context.payload.pull_request) { - return context.payload.pull_request.number; - } - // Workaround for https://github.com/orgs/community/discussions/25220 - - const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - const artifact = artifacts.find(artifact => artifact.name == 'github-pr-info'); - if (!artifact) { - throw new Error('Cannot find github-pr-info.txt artifact'); - } - - const { data } = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: artifact.id, - archive_format: 'zip', - }); - - await fs.writeFile('pr-info.zip', Buffer.from(data)); - await exec.exec('unzip', ['pr-info.zip']); - return await fs.readFile('github-pr-info.txt', 'utf8'); - } - - const prNumber = await derivePRNumber(); - - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - }); - - core.info(`Checking if the PR ${prNumber} is labeled with Playground...`); - if (!pr.labels.some(label => label.name == 'Playground')) { - core.info(`The PR is not labeled with Playground.`); - return; - } - - core.info(`Checking if the build is successful for ${pr.head.ref} in ${pr.head.repo.owner.login}/${pr.head.repo.name}...`); - const buildRun = await findSuccessfuBuildRun(pr); - if (!buildRun) { - core.info(`No successful build run found for ${buildWorkflowPath} on ${pr.head.ref} yet.`); - return; - } - core.info(`Found a successful build run: ${buildRun.html_url}`); - - const runLink = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; - const magicComment = ``; - const comment = `${magicComment} - **Try on Playground**: https://ruby.github.io/play-ruby?run=${buildRun.id} - This is an automated comment by [\`pr-playground.yml\`](${runLink}) workflow. - `; - core.info(`Comment: ${comment}`); - await postComment(comment, pr); - diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 5d4474d978f51a..00000000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Start release workflow -on: - push: - tags: - - '*' - -jobs: - notify: - runs-on: ubuntu-latest - steps: - - name: Build release package - run: | - curl -L -X POST \ - -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/ruby/actions/dispatches \ - -d '{"event_type": "${{ github.ref }}"}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index aeb116bf453032..00000000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,107 +0,0 @@ -name: Publish Ruby packages - -on: - repository_dispatch: - types: - - release - workflow_dispatch: - inputs: - version: - description: 'Version of the Ruby package to release' - required: true - default: '3.3.4' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.3.4 - - - name: Store Ruby version - run: | - echo "RUBY_VERSION=${{ github.event.client_payload.version || github.event.inputs.version }}" >> $GITHUB_ENV - - - name: Store ABI version - run: echo "ABI_VERSION=$(echo ${{ env.RUBY_VERSION }} | cut -d '.' -f 1-2)" >> $GITHUB_ENV - - - name: Copy draft package `/tmp` to `/pub` directory - run: tool/release.sh ${{ env.RUBY_VERSION }} - env: - AWS_ACCESS_KEY_ID: ${{ secrets.FTP_R_L_O_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.FTP_R_L_O_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: us-west-2 - - - name: Purge URLs of release package - run: | - curl -X POST \ - -H "Fastly-Key: ${{ secrets.FASTLY_PURGE_TOKEN }}" \ - https://api.fastly.com/purge/cache.ruby-lang.org/pub/ruby/${{ env.ABI_VERSION }}/ruby-${{ env.RUBY_VERSION }}.tar.gz - curl -X POST \ - -H "Fastly-Key: ${{ secrets.FASTLY_PURGE_TOKEN }}" \ - https://api.fastly.com/purge/cache.ruby-lang.org/pub/ruby/${{ env.ABI_VERSION }}/ruby-${{ env.RUBY_VERSION }}.tar.xz - curl -X POST \ - -H "Fastly-Key: ${{ secrets.FASTLY_PURGE_TOKEN }}" \ - https://api.fastly.com/purge/cache.ruby-lang.org/pub/ruby/${{ env.ABI_VERSION }}/ruby-${{ env.RUBY_VERSION }}.zip - - - name: Create a release on GitHub - run: | - RELEASE_TAG=$(echo v${{ env.RUBY_VERSION }} | sed 's/\./_/g') - echo $RELEASE_TAG - PREVIOUS_RELEASE_TAG=$(echo $RELEASE_TAG | awk 'BEGIN {FS="_"; OFS="_"}{ $NF=$NF-1; print }') - echo $PREVIOUS_RELEASE_TAG - tool/gen-github-release.rb $PREVIOUS_RELEASE_TAG $RELEASE_TAG --no-dry-run - env: - GITHUB_TOKEN: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }} - - - name: Update versions index - run: | - curl -L -X POST \ - -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/ruby/actions/dispatches \ - -d '{"event_type": "update_index"}' - - - name: Build and push Docker images - run: | - curl -L -X POST \ - -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/ruby/docker-images/dispatches \ - -d '{"event_type": "build", "client_payload": {"ruby_version": "${{ env.RUBY_VERSION }}"}}' - - - name: Build snapcraft packages - run: | - curl -L -X POST \ - -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/ruby/snap.ruby/dispatches \ - -d '{"event_type": "build", "client_payload": {"ruby_version": "${{ env.RUBY_VERSION }}"}}' - - - name: Store the latest LTS version of OpenSSL - run: | - echo "OPENSSL_VERSION=`curl -s https://api.github.com/repos/openssl/openssl/releases | jq -r '.[].tag_name | select(startswith("openssl-3.0"))' | sort -Vr | head -n1 | cut -d'-' -f2`" >> $GITHUB_ENV - - - name: Update ruby-build definition - run: | - curl -L -X POST \ - -H "Authorization: Bearer ${{ secrets.RUBY_BUILD_WORKFLOW_TOKEN }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/rbenv/ruby-build/dispatches \ - -d '{"event_type": "update-ruby", "client_payload": {"ruby_version": "${{ env.RUBY_VERSION }}", "openssl_version": "${{ env.OPENSSL_VERSION }}"}}' - - - name: Update all-ruby definition - run: | - curl -L -X POST \ - -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/ruby/all-ruby/dispatches \ - -d '{"event_type": "update"}' diff --git a/.github/workflows/rust-warnings.yml b/.github/workflows/rust-warnings.yml deleted file mode 100644 index 98ddfead5c8522..00000000000000 --- a/.github/workflows/rust-warnings.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Surface Rust warnings on PRs that touch any Rust code. -# Not a required check so we never block people over new warnings -# that might come from a new Rust version being released. -name: Rust warnings -on: - pull_request: - types: - - opened - - synchronize - - reopened - paths: - - '**.rs' - - '!**.inc.rs' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - - runs-on: ubuntu-24.04 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Install Rust - run: rustup default beta - - - name: Rust warnings - shell: bash - run: | - set -eu - cargo check --quiet --all-features --message-format=json \ - | jq -r 'select(.message.level == "warning" or .message.level == "error") | .message.rendered' \ - | tee messages.txt - (exit "${PIPESTATUS[0]}") && ! grep --quiet '[^[:space:]]' messages.txt diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml deleted file mode 100644 index c997a6a453be76..00000000000000 --- a/.github/workflows/scorecards.yml +++ /dev/null @@ -1,78 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third-party and are governed by separate terms of service, privacy -# policy, and support documentation. - -name: Scorecard supply-chain security -on: - # For Branch-Protection check. Only the default branch is supported. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection - branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '39 3 * * 5' - # push: - # branches: [ "master" ] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecard analysis - runs-on: ubuntu-latest - # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. - if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results and get a badge (see publish_results below). - id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read - - steps: - - name: "Checkout code" - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 - with: - results_file: results.sarif - results_format: sarif - # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecard on a *private* repository - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. - # repo_token: ${{ secrets.SCORECARD_TOKEN }} - - # Public repositories: - # - Publish results to OpenSSF REST API for easy access by consumers - # - Allows the repository to include the Scorecard badge. - # - See https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories: - # - `publish_results` will always be set to `false`, regardless - # of the value entered here. - publish_results: true - - # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore - # file_mode: git - - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard (optional). - # Commenting out will disable upload of results to your repo's Code Scanning dashboard - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml deleted file mode 100644 index 81b242bbca52ae..00000000000000 --- a/.github/workflows/spec_guards.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Rubyspec Version Guards Check - -on: - push: - paths: - - '.github/workflows/spec_guards.yml' - - 'spec/**' - - '!spec/*.md' - pull_request: - paths: - - '.github/workflows/spec_guards.yml' - - 'spec/**' - - '!spec/*.md' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - rubyspec: - name: Rubyspec - - runs-on: ubuntu-22.04 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - strategy: - matrix: - # Specs from ruby/spec should still run on all supported Ruby versions. - # This also ensures the needed ruby_version_is guards are there, see spec/README.md. - ruby: - - ruby-3.2 - - ruby-3.3 - - ruby-3.4 - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: ${{ matrix.ruby }} - bundler: none - - - run: gem install webrick - - - run: ruby ../mspec/bin/mspec - working-directory: spec/ruby - env: - CHECK_LEAKS: true - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.ruby }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/ubuntu-ibm.yml b/.github/workflows/ubuntu-ibm.yml deleted file mode 100644 index 8bcce6e4a1867e..00000000000000 --- a/.github/workflows/ubuntu-ibm.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Ubuntu IBM -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - # Do not use paths-ignore for required status checks - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - strategy: - matrix: - test_task: [check] - configure: [''] - os: - - ubuntu-24.04-ppc64le - - ubuntu-24.04-s390x - # Add a x86_64 case to make this CI pass on fork repositories. - - ubuntu-24.04 - # The ppc64le/s390x runners work only in the registered repositories. - # They don't work in forked repositories. - # https://github.com/IBM/actionspz/blob/main/docs/FAQ.md#what-about-forked-repos - upstream: - - ${{ github.repository == 'ruby/ruby' }} - exclude: - - os: ubuntu-24.04-ppc64le - upstream: false - - os: ubuntu-24.04-s390x - upstream: false - - os: ubuntu-24.04 - upstream: true - fail-fast: false - - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - RUBY_DEBUG: ci - - runs-on: ${{ matrix.os }} - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/ubuntu - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - if: ${{ !endsWith(matrix.os, 'ppc64le') && !endsWith(matrix.os, 's390x') }} - - # Avoid possible test failures with the zlib applying the following patch - # on s390x CPU architecture. - # https://github.com/madler/zlib/pull/410 - - name: Disable DFLTCC - run: echo "DFLTCC=0" >> $GITHUB_ENV - working-directory: - if: ${{ endsWith(matrix.os, 's390x') }} - - # A temporary workaround: Set HOME env to pass the step - # ./.github/actions/setup/directories. - # https://github.com/IBM/actionspz/issues/30 - - name: Set HOME env - run: | - echo "HOME: ${HOME}" - echo "HOME=$(ls -d ~)" >> $GITHUB_ENV - working-directory: - if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - clean: true - dummy-files: ${{ matrix.test_task == 'check' }} - # Set fetch-depth: 10 so that Launchable can receive commits information. - fetch-depth: 10 - - - name: Run configure - env: - configure: ${{ matrix.configure }} - run: >- - ../src/configure -C --disable-install-doc ${configure:-cppflags=-DRUBY_DEBUG} - - - run: make - - - run: make hello - - - name: runirb - run: | - echo IRB::VERSION | make runirb RUNOPT="-- -f" - - - name: Set test options for skipped tests - run: | - set -x - TESTS="$(echo "${{ matrix.skipped_tests }}" | sed 's| |$$/ -n!/|g;s|^|-n!/|;s|$|$$/|')" - echo "TESTS=${TESTS}" >> $GITHUB_ENV - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - - - name: Set up Launchable - id: launchable - uses: ./.github/actions/launchable/setup - with: - os: ${{ matrix.os }} - test-opts: ${{ matrix.configure }} - launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} - builddir: build - srcdir: src - continue-on-error: true - timeout-minutes: 3 - - # A temporary workaround: Set the user's primary group to avoid a mismatch - # between the group IDs of "id -g" and C function getpwuid(uid_t uid) - # pw_gid. - # https://github.com/IBM/actionspz/issues/31 - - name: Set user's group id - run: sudo usermod -g "$(id -g)" runner - if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} - - - name: make ${{ matrix.test_task }} - run: | - test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") - test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") - - make -s ${{ matrix.test_task }} \ - ${TESTS:+TESTS="$TESTS"} \ - ${{ !contains(matrix.test_task, 'bundle') && 'RUBYOPT=-w' || '' }} - timeout-minutes: ${{ matrix.timeout || 40 }} - env: - RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - PRECHECK_BUNDLED_GEMS: 'no' - LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} - LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - - - name: make skipped tests - run: | - make -s test-all TESTS="${TESTS//-n!\//-n/}" - env: - GNUMAKEFLAGS: '' - RUBY_TESTOPTS: '-v --tty=no' - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - - name: test-pc - run: | - DESTDIR=${RUNNER_TEMP-${TMPDIR-/tmp}}/installed - make test-pc "DESTDIR=$DESTDIR" - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.test_task }} ${{ matrix.configure }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml deleted file mode 100644 index 6249418a9abafb..00000000000000 --- a/.github/workflows/ubuntu.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Ubuntu -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - # Do not use paths-ignore for required status checks - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - strategy: - matrix: - # We enumerate every job in matrix.include to save build time - include: - - test_task: check - configure: 'cppflags=-DVM_CHECK_MODE' - - test_task: check - arch: i686 - - test_task: check - configure: '--disable-yjit' - - test_task: check - configure: '--enable-shared --enable-load-relative' - - test_task: test-bundler-parallel - timeout: 50 - - test_task: test-bundled-gems - - test_task: check - os: ubuntu-24.04 - extra_checks: [capi] - # ubuntu-24.04-arm jobs don't start on ruby/ruby as of 2025-09-04 - #- test_task: check - # os: ubuntu-24.04-arm - fail-fast: false - - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - RUBY_DEBUG: ci - - runs-on: ${{ matrix.os || 'ubuntu-22.04' }} - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/ubuntu - with: - arch: ${{ matrix.arch }} - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - if: ${{ !endsWith(matrix.os, 'arm') }} - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - clean: true - dummy-files: ${{ matrix.test_task == 'check' }} - # Set fetch-depth: 10 so that Launchable can receive commits information. - fetch-depth: 10 - - - name: Run configure - env: - arch: ${{ matrix.arch }} - configure: ${{ matrix.configure }} - run: >- - $SETARCH ../src/configure -C --disable-install-doc ${configure:-cppflags=-DRUBY_DEBUG} - ${arch:+--target=$arch-$OSTYPE --host=$arch-$OSTYPE} - - - run: $SETARCH make prepare-gems - if: ${{ matrix.test_task == 'test-bundled-gems' }} - - - run: $SETARCH make - - - run: $SETARCH make hello - - - name: runirb - run: | - echo IRB::VERSION | $SETARCH make runirb RUNOPT="-- -f" - - - name: Set test options for skipped tests - run: | - set -x - TESTS="$(echo "${{ matrix.skipped_tests }}" | sed 's| |$$/ -n!/|g;s|^|-n!/|;s|$|$$/|')" - echo "TESTS=${TESTS}" >> $GITHUB_ENV - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - - - name: Set up Launchable - id: launchable - uses: ./.github/actions/launchable/setup - with: - os: ${{ matrix.os || 'ubuntu-22.04' }} - test-opts: ${{ matrix.configure }} - launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} - builddir: build - srcdir: src - continue-on-error: true - timeout-minutes: 3 - - - name: make ${{ matrix.test_task }} - run: | - test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") - test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") - - $SETARCH make -s ${{ matrix.test_task }} \ - ${TESTS:+TESTS="$TESTS"} \ - ${{ !contains(matrix.test_task, 'bundle') && 'RUBYOPT=-w' || '' }} - timeout-minutes: ${{ matrix.timeout || 40 }} - env: - RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - PRECHECK_BUNDLED_GEMS: 'no' - LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} - LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - - - name: make skipped tests - run: | - $SETARCH make -s test-all TESTS="${TESTS//-n!\//-n/}" - env: - GNUMAKEFLAGS: '' - RUBY_TESTOPTS: '-v --tty=no' - if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - - name: test-pc - run: | - DESTDIR=${RUNNER_TEMP-${TMPDIR-/tmp}}/installed - $SETARCH make test-pc "DESTDIR=$DESTDIR" - - - name: CAPI extensions - uses: ./.github/actions/capiext - with: - builddir: build - make: '$SETARCH make' - env: - RUBY_TESTOPTS: '-v --tty=no' - if: ${{ contains(matrix.extra_checks, 'capi') }} - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - - result: - if: ${{ always() }} - name: ${{ github.workflow }} result - runs-on: ubuntu-latest - needs: [make] - steps: - - run: exit 1 - working-directory: - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} - -defaults: - run: - working-directory: build diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml deleted file mode 100644 index 011351161aea07..00000000000000 --- a/.github/workflows/wasm.yml +++ /dev/null @@ -1,179 +0,0 @@ -name: WebAssembly -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: # added using https://github.com/step-security/secure-workflows - contents: read - -jobs: - make: - strategy: - matrix: - entry: -# # wasmtime can't compile non-optimized Asyncified binary due to locals explosion -# - { name: O0-debuginfo, optflags: '-O0', debugflags: '-g', wasmoptflags: '-O1' } -# - { name: O1, optflags: '-O1', debugflags: '' , wasmoptflags: '-O1' } - - { name: O2, optflags: '-O2', debugflags: '', wasmoptflags: '-O2' } -# - { name: O3, optflags: '-O3', debugflags: '' , wasmoptflags: '-O3' } -# # -O4 is equivalent to -O3 in clang, but it's different in wasm-opt -# - { name: O4, optflags: '-O3', debugflags: '' , wasmoptflags: '-O4' } -# - { name: Oz, optflags: '-Oz', debugflags: '' , wasmoptflags: '-Oz' } - fail-fast: false - - env: - RUBY_TESTOPTS: '-q --tty=no' - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - WASI_SDK_VERSION_MAJOR: 25 - WASI_SDK_VERSION_MINOR: 0 - BINARYEN_VERSION: 113 - WASMTIME_VERSION: v15.0.0 - - runs-on: ubuntu-22.04 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - makeup: true - - - name: Install libraries - run: | - set -ex - sudo apt-get update -q || : - sudo apt-get install --no-install-recommends -q -y ruby make autoconf git wget - - wasi_sdk_deb="wasi-sdk-${WASI_SDK_VERSION_MAJOR}.${WASI_SDK_VERSION_MINOR}-x86_64-linux.deb" - wget "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION_MAJOR}/${wasi_sdk_deb}" - sudo dpkg -i "$wasi_sdk_deb" - rm -f "$wasi_sdk_deb" - - mkdir build-sdk - pushd build-sdk - - wasmtime_url="https://github.com/bytecodealliance/wasmtime/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz" - wget -O - "$wasmtime_url" | tar xJf - - sudo ln -fs "$PWD/wasmtime-${WASMTIME_VERSION}-x86_64-linux/wasmtime" /usr/local/bin/wasmtime - - binaryen_tarball="binaryen-version_${BINARYEN_VERSION}-x86_64-linux.tar.gz" - binaryen_url="https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_VERSION}/${binaryen_tarball}" - wget -O - "$binaryen_url" | tar xfz - - sudo ln -fs "$PWD/binaryen-version_${BINARYEN_VERSION}/bin/wasm-opt" /usr/local/bin/wasm-opt - working-directory: src - - - name: Set ENV - run: | - echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - ruby-version: '3.1' - bundler: none - - - name: Build baseruby - run: | - set -ex - mkdir ../baseruby - pushd ../baseruby - ../src/configure --prefix=$PWD/install - make - make install - - - name: Download config.guess with wasi version - run: | - rm tool/config.guess tool/config.sub - ruby tool/downloader.rb -d tool -e gnu config.guess config.sub - working-directory: src - - - name: Run configure - run: | - ../src/configure \ - --host wasm32-unknown-wasi \ - --with-baseruby=$PWD/../baseruby/install/bin/ruby \ - --with-static-linked-ext \ - --with-ext=cgi/escape,continuation,coverage,date,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,json,json/generator,json/parser,objspace,pathname,rbconfig/sizeof,ripper,stringio,strscan,monitor \ - LDFLAGS=" \ - -Xlinker --stack-first \ - -Xlinker -z -Xlinker stack-size=16777216 \ - " \ - optflags="${{ matrix.entry.optflags }}" \ - debugflags="${{ matrix.entry.debugflags }}" \ - wasmoptflags="${{ matrix.entry.wasmoptflags }} ${{ matrix.entry.debugflags }}" - - # miniruby may not be built when cross-compling - - run: make mini ruby - - - run: make install DESTDIR=$PWD/../install - - run: tar cfz ../install.tar.gz -C ../install . - - - name: Upload artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ruby-wasm-install - path: ${{ github.workspace }}/install.tar.gz - - name: Show Playground URL to try the build - run: | - echo "Try on Playground: https://ruby.github.io/play-ruby?run=$GITHUB_RUN_ID" >> $GITHUB_STEP_SUMMARY - - - name: Run basictest - run: wasmtime run ./../build/miniruby --mapdir /::./ -- basictest/test.rb - working-directory: src - - - name: Run bootstraptest (no thread) - run: | - NO_THREAD_TESTS="$(grep -L Thread -R ./bootstraptest | awk -F/ '{ print $NF }' | uniq | sed -n 's/test_\(.*\).rb/\1/p' | paste -s -d, -)" - ruby ./bootstraptest/runner.rb --ruby="$(which wasmtime) run $PWD/../build/ruby --mapdir /::./ -- " --verbose "--sets=$NO_THREAD_TESTS" - working-directory: src - - - uses: ./.github/actions/slack - with: - label: ${{ matrix.entry.name }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - - # Workaround for https://github.com/orgs/community/discussions/25220 - - name: Save Pull Request number - if: ${{ github.event_name == 'pull_request' }} - run: echo "${{ github.event.pull_request.number }}" >> ${{ github.workspace }}/github-pr-info.txt - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: ${{ github.event_name == 'pull_request' }} - with: - name: github-pr-info - path: ${{ github.workspace }}/github-pr-info.txt - -defaults: - run: - working-directory: build diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index 1083de63b33567..00000000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,220 +0,0 @@ -name: Windows -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - # Do not use paths-ignore for required status checks - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - make: - strategy: - matrix: - include: - - os: 2022 - vc: 2022 - test_task: check - - os: 2025 - vc: 2022 - test_task: check - - os: 11-arm - test_task: 'btest test-basic test-tool' # check and test-spec are broken yet. - target: arm64 - - os: 2025 - vc: 2022 - test_task: test-bundled-gems - fail-fast: false - - runs-on: windows-${{ matrix.os }} - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - name: Windows ${{ matrix.os }}/Visual C++ ${{ matrix.vc }} (${{ matrix.test_task }}) - - env: - GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - VCPKG_DEFAULT_TRIPLET: ${{ matrix.target || 'x64' }}-windows - - steps: - - run: md build - working-directory: - - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 - with: - # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head - ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} - bundler: none - windows-toolchain: none - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - sparse-checkout-cone-mode: false - sparse-checkout: /.github - - - uses: ./.github/actions/setup/directories - with: - srcdir: src - builddir: build - - - name: Install tools with scoop - run: | - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - iwr -useb get.scoop.sh | iex - Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH - scoop install vcpkg uutils-coreutils cmake@3.31.6 - shell: pwsh - - - name: Restore vcpkg artifact - id: restore-vcpkg - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: src\vcpkg_installed - key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} - - - name: Install libraries with vcpkg - run: | - vcpkg install --vcpkg-root=%USERPROFILE%\scoop\apps\vcpkg\current - working-directory: src - if: ${{ ! steps.restore-vcpkg.outputs.cache-hit }} - - - name: Save vcpkg artifact - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: src\vcpkg_installed - key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} - if: ${{ ! steps.restore-vcpkg.outputs.cache-hit && (github.ref_name == 'master' || startsWith(github.ref_name, 'ruby_')) }} - - - name: setup env - # Available Ruby versions: https://github.com/actions/runner-images/blob/main/images/windows/Windows2019-Readme.md#ruby - # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. - # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 - run: | - ::- Set up VC ${{ matrix.vc }} - set | uutils sort > old.env - call ..\src\win32\vssetup.cmd ^ - -arch=${{ matrix.target || 'amd64' }} ^ - ${{ matrix.vcvars && '-vcvars_ver=' || '' }}${{ matrix.vcvars }} - nmake -f nul - set TMP=%RUNNER_TEMP% - set TEMP=%RUNNER_TEMP% - set MAKEFLAGS=l - set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul - set RUBY_OPT_DIR=%GITHUB_WORKSPACE:\=/%/src/vcpkg_installed/%VCPKG_DEFAULT_TRIPLET% - set | uutils sort > new.env - uutils comm -13 old.env new.env >> %GITHUB_ENV% - del *.env - - - name: baseruby version - run: ruby -v - - - name: compiler version - run: cl - - - name: volume info - run: Get-Volume - shell: pwsh - - # TODO: We should use `../src` instead of `D:/a/ruby/ruby/src` - - name: Configure - run: >- - ../src/win32/configure.bat --disable-install-doc - --with-opt-dir=%RUBY_OPT_DIR% - --with-gmp - - - run: nmake prepare-vcpkg - - - run: nmake incs - - - run: nmake extract-extlibs - - # On all other platforms, test-spec depending on extract-gems (in common.mk) is enough. - # But not for this Visual Studio workflow. So here we extract gems before building. - - run: nmake extract-gems - - # windows-11-arm runner cannot run `ruby tool/file2lastrev.rb --revision.h --output=revision.h` - - name: make revision.h - run: | - if not exist revision.h ( - for /f "tokens=1-3" %%I in ('git log -1 "--date=format-local:%%F %%T" "--format=%%H %%cd" @') do ( - set rev=%%I - set dt=%%J - set tm=%%K - ) - call set yy=%%dt:~0,4%% - call set /a mm=100%%dt:~5,2%% %%%% 100 - call set /a dd=100%%dt:~8,2%% %%%% 100 - call set branch=%%GITHUB_REF:refs/heads/=%% - ( - call echo #define RUBY_REVISION "%%rev:~,10%%" - call echo #define RUBY_FULL_REVISION "%%rev%%" - call echo #define RUBY_BRANCH_NAME "%%branch%%" - call echo #define RUBY_RELEASE_DATETIME "%%dt%%T%%tm%%" - call echo #define RUBY_RELEASE_YEAR %%yy%% - call echo #define RUBY_RELEASE_MONTH %%mm%% - call echo #define RUBY_RELEASE_DAY %%dd%% - ) > revision.h - copy /y NUL .revision.time - ) - type revision.h - env: - TZ: UTC - - - run: nmake - - - name: Set up Launchable - uses: ./.github/actions/launchable/setup - with: - os: windows-${{ matrix.os }} - launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} - builddir: build - srcdir: src - test-task: ${{ matrix.test_task || 'check' }} - continue-on-error: true - if: ${{ matrix.test_task != 'test-bundled-gems' }} - timeout-minutes: 3 - - - run: nmake ${{ matrix.test_task || 'check' }} - env: - RUBY_TESTOPTS: -j${{ env.TEST_JOBS || 4 }} - timeout-minutes: 70 - - - uses: ./.github/actions/slack - with: - label: Windows ${{ matrix.os }} / VC ${{ matrix.vc }} / ${{ matrix.test_task || 'check' }} - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} - - result: - if: ${{ always() }} - name: ${{ github.workflow }} result - runs-on: windows-latest - needs: [make] - steps: - - run: exit 1 - working-directory: - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} - -defaults: - run: - working-directory: build - shell: cmd diff --git a/.github/workflows/wsl.yml b/.github/workflows/wsl.yml deleted file mode 100644 index 640f18ce42e876..00000000000000 --- a/.github/workflows/wsl.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Ubuntu on WSL - -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - # Do not use paths-ignore for required status checks - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - merge_group: - -jobs: - wsl: - runs-on: windows-2025 - - if: >- - ${{!(false - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') - )}} - - steps: - - name: Install or update WSL - uses: Ubuntu/WSL/.github/actions/wsl-install@main - with: - distro: Ubuntu-24.04 - - - name: Install dependencies - uses: Ubuntu/WSL/.github/actions/wsl-bash@main - with: - distro: Ubuntu-24.04 - working-dir: /tmp/github/ - exec: | - DEBIAN_FRONTEND=noninteractive sudo apt update - DEBIAN_FRONTEND=noninteractive sudo apt install -y ruby build-essential autoconf libssl-dev libyaml-dev zlib1g-dev libgmp-dev libffi-dev - - - name: Check out the repository - uses: Ubuntu/WSL/.github/actions/wsl-checkout@main - with: - distro: Ubuntu-24.04 - working-dir: /tmp/github/ - submodules: true - - - name: Build - uses: Ubuntu/WSL/.github/actions/wsl-bash@main - with: - distro: Ubuntu-24.04 - working-dir: /tmp/github/ - exec: | - ./autogen.sh - ./configure --disable-install-doc - make ruby -j4 - make extract-gems - make -j4 - - - name: Test - uses: Ubuntu/WSL/.github/actions/wsl-bash@main - with: - distro: Ubuntu-24.04 - working-dir: /tmp/github/ - exec: | - ./ruby -v - # make check TESTS="-j4" MSPECOPT="-j" From 83e3b07eb3e55e83da6c9d31dfdc2b84aae1e23e Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 16:33:41 -0400 Subject: [PATCH 101/104] yaml --- .github/workflows/yjit-macos.yml | 10 ++++++++-- .github/workflows/yjit-ubuntu.yml | 9 ++++++++- 2 files changed, 16 insertions(+), 3 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..adf7c9404cc119 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -169,7 +169,9 @@ jobs: if: ${{ contains(matrix.configure, 'jit=dev') }} - name: Enable YJIT through ENV - run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + 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 }} From 9310af698f203b2e0d1149c9fcd7128e5f838725 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 16:38:23 -0400 Subject: [PATCH 102/104] induce crash --- test/ruby/lbtest.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/ruby/lbtest.rb b/test/ruby/lbtest.rb index c7822c9e9a2cce..9b6abe870e0c7e 100644 --- a/test/ruby/lbtest.rb +++ b/test/ruby/lbtest.rb @@ -1,5 +1,8 @@ # frozen_string_literal: false +require 'fiddle' +Fiddle::Pointer.new(1)[0] + class LocalBarrier def initialize(n) @wait = Thread::Queue.new From 68f22b94eb01aa55c81cf6e8721f5599c3c58279 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 17:15:23 -0400 Subject: [PATCH 103/104] green --- test/-ext-/bug_reporter/test_bug_reporter.rb | 2 +- test/ruby/test_rubyoptions.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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'}) From e3cb6d8085248c2f54da1cb4953c570598a5e801 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Sep 2025 18:02:35 -0400 Subject: [PATCH 104/104] green darwin --- test/ruby/test_vm_dump.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb index a3e7b69913b7d2..fe358fb65af395 100644 --- a/test/ruby/test_vm_dump.rb +++ b/test/ruby/test_vm_dump.rb @@ -5,7 +5,8 @@ class TestVMDump < Test::Unit::TestCase def assert_darwin_vm_dump_works(args, timeout=nil) - assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300) + env = {"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil} + assert_in_out_err(env, args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300) end def test_darwin_invalid_call