diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml index e5389bcf22b7bf..1be2b11b8634e4 100644 --- a/.github/workflows/auto_request_review.yml +++ b/.github/workflows/auto_request_review.yml @@ -14,8 +14,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' && github.base_ref == 'master' }} steps: - name: Request review based on files changes and/or groups the author belongs to - # Using a fork until https://github.com/necojackarc/auto-request-review/pull/135 is merged - uses: k0kubun/auto-request-review@0df295a0ff5c5d302770f589497280132131c63d # master + uses: necojackarc/auto-request-review@5d3060495e58e9cb41f51de50e808d3135d5374e # master with: # scope: public_repo token: ${{ secrets.MATZBOT_AUTO_REQUEST_REVIEW_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index 521129d92d2863..33010e65fb63ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,11 +36,15 @@ yjit = [ "dep:yjit" ] zjit = [ "dep:zjit" ] [profile.dev] -opt-level = 0 +opt-level = 1 # On 0, functions use so much stack space that we get stray `SystemStackError`s debug = true debug-assertions = true overflow-checks = true +[profile.test] +inherits = "dev" +opt-level = 0 + [profile.dev_nodebug] inherits = "dev" diff --git a/common.mk b/common.mk index 3105c0977cc64e..a43b9304f773c5 100644 --- a/common.mk +++ b/common.mk @@ -1578,7 +1578,7 @@ no-test-bundled-gems-precheck: update-default-gemspecs: $(TEST_RUNNABLE)-update-default-gemspecs no-update-default-gemspecs: -yes-update-default-gemspecs: $(PRECHECK_BUNDLED_GEMS:yes=main) +yes-update-default-gemspecs: $(PRECHECK_BUNDLED_GEMS:yes=main) $(PROGRAM) @$(MAKEDIRS) $(srcdir)/.bundle/specifications @$(XRUBY) -W0 -C "$(srcdir)" -rrubygems \ -e "destdir = ARGV.shift" \ diff --git a/cont.c b/cont.c index e5239635081629..c44d233a9fcdb8 100644 --- a/cont.c +++ b/cont.c @@ -1480,7 +1480,7 @@ rb_yjit_cancel_jit_return(void *leave_exit, void *leave_exception) const rb_control_frame_t *cfp = cont->ec->cfp; while (!RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(cont->ec, cfp)) { - if (CFP_JIT_RETURN(cfp) && cfp->jit_return != leave_exception) { + if (cfp->jit_return && cfp->jit_return != leave_exception) { ((rb_control_frame_t *)cfp)->jit_return = leave_exit; } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); diff --git a/test/.excludes-zjit/TestOpenURI.rb b/test/.excludes-zjit/TestOpenURI.rb deleted file mode 100644 index f08a2d7e89b201..00000000000000 --- a/test/.excludes-zjit/TestOpenURI.rb +++ /dev/null @@ -1,3 +0,0 @@ -if RUBY_PLATFORM =~ /linux/ - exclude(/test_/, 'randomly fails with SystemStackError (Shopify/ruby#964)') -end diff --git a/test/.excludes-zjit/TestOpenURIProxy.rb b/test/.excludes-zjit/TestOpenURIProxy.rb deleted file mode 100644 index f08a2d7e89b201..00000000000000 --- a/test/.excludes-zjit/TestOpenURIProxy.rb +++ /dev/null @@ -1,3 +0,0 @@ -if RUBY_PLATFORM =~ /linux/ - exclude(/test_/, 'randomly fails with SystemStackError (Shopify/ruby#964)') -end diff --git a/test/.excludes-zjit/TestOpenURISSL.rb b/test/.excludes-zjit/TestOpenURISSL.rb deleted file mode 100644 index f08a2d7e89b201..00000000000000 --- a/test/.excludes-zjit/TestOpenURISSL.rb +++ /dev/null @@ -1,3 +0,0 @@ -if RUBY_PLATFORM =~ /linux/ - exclude(/test_/, 'randomly fails with SystemStackError (Shopify/ruby#964)') -end diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 0679180ce9fbfb..6f08b4089cf19a 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -80,6 +80,8 @@ def test_read_timeout sock.print "Content-Length: 4\r\n\r\n" sleep 1 sock.print "ab\r\n" + rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED + # expected when client times out and closes the connection ensure sock.close end diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 01e5cc68f6eadc..294498c30f25f8 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -538,7 +538,7 @@ def test_named_structs_are_not_rooted omit 'skip on riscv64-linux CI machine. See https://github.com/ruby/ruby/pull/13422' if ENV['RUBY_DEBUG'] == 'ci' && /riscv64-linux/ =~ RUBY_DESCRIPTION # [Bug #20311] - assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true, limit: 2.2) code = proc do Struct.new("A") Struct.send(:remove_const, :A) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 2dea3359c185bb..c3d9dcf56dabae 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -798,7 +798,7 @@ def test_handle_interrupt_invalid_argument def for_test_handle_interrupt_with_return Thread.handle_interrupt(Object => :never){ - Thread.current.raise RuntimeError.new("have to be rescured") + Thread.current.raise RuntimeError.new("have to be rescued") return } rescue @@ -815,7 +815,7 @@ def test_handle_interrupt_with_break assert_nothing_raised do begin Thread.handle_interrupt(Object => :never){ - Thread.current.raise RuntimeError.new("have to be rescured") + Thread.current.raise RuntimeError.new("have to be rescued") break } rescue @@ -1592,10 +1592,9 @@ def frame_for_deadlock_test_2 INPUT end - # [Bug #21342] def test_unlock_locked_mutex_with_collected_fiber - bug21127 = '[ruby-core:120930] [Bug #21127]' - assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}", bug21127) + bug21342 = '[ruby-core:122121] [Bug #21342]' + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}", bug21342) begin; 5.times do m = Mutex.new @@ -1667,7 +1666,7 @@ def test_mn_threads_sub_millisecond_sleep # [Bug #21926] def test_thread_join_during_finalizers - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60) begin; require 'open3' @@ -1690,7 +1689,7 @@ def self.make_finalizer(stdin, stdout, stderr, wait_thread) end end - 50.times { ProcessWrapper.new } + 20.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } end; diff --git a/vm.c b/vm.c index 0398b9f74c9683..ad8a22ef4b2fc8 100644 --- a/vm.c +++ b/vm.c @@ -2852,7 +2852,7 @@ zjit_materialize_frames(rb_control_frame_t *cfp) if (!rb_zjit_enabled_p) return; while (true) { - if (CFP_JIT_RETURN(cfp)) { + if (CFP_ZJIT_FRAME(cfp)) { const zjit_jit_frame_t *jit_frame = (const zjit_jit_frame_t *)cfp->jit_return; cfp->pc = jit_frame->pc; cfp->_iseq = (rb_iseq_t *)jit_frame->iseq; @@ -3671,7 +3671,7 @@ rb_execution_context_update(rb_execution_context_t *ec) while (cfp != limit_cfp) { const VALUE *ep = cfp->ep; cfp->self = rb_gc_location(cfp->self); - if (CFP_JIT_RETURN(cfp)) { + if (CFP_ZJIT_FRAME(cfp)) { rb_zjit_jit_frame_update_references((zjit_jit_frame_t *)cfp->jit_return); // block_code must always be relocated. For ISEQ frames, the JIT caller // may have written it (gen_block_handler_specval) for passing blocks. diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 705199ea1fd4c3..32244ac1334bdb 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -3219,7 +3219,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, if (UNLIKELY(!ISEQ_BODY(iseq)->param.flags.use_block && calling->block_handler != VM_BLOCK_HANDLER_NONE && !(vm_ci_flag(calling->cd->ci) & (VM_CALL_OPT_SEND | VM_CALL_SUPER)))) { - warn_unused_block(vm_cc_cme(cc), iseq, (void *)ec->cfp->pc); + warn_unused_block(vm_cc_cme(cc), iseq, (void *)CFP_PC(ec->cfp)); } if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) { diff --git a/yjit.c b/yjit.c index 46565cb6c0d035..2e7216a1915406 100644 --- a/yjit.c +++ b/yjit.c @@ -480,7 +480,7 @@ rb_yjit_set_exception_return(rb_control_frame_t *cfp, void *leave_exit, void *le // If it's a FINISH frame, just normally exit with a non-Qundef value. cfp->jit_return = leave_exit; } - else if (CFP_JIT_RETURN(cfp)) { + else if (cfp->jit_return) { while (!VM_FRAME_FINISHED_P(cfp)) { if (cfp->jit_return == leave_exit) { // Unlike jit_exec(), leave_exit is not safe on a non-FINISH frame on diff --git a/zjit.h b/zjit.h index d67c8b82f21efe..065f08e32f9994 100644 --- a/zjit.h +++ b/zjit.h @@ -75,7 +75,7 @@ enum zjit_poison_values { // YJIT also uses jit_return (as a return address), so this must only return // non-NULL when ZJIT is enabled and has set jit_return to a JITFrame pointer. static inline void * -CFP_JIT_RETURN(const rb_control_frame_t *cfp) +CFP_ZJIT_FRAME(const rb_control_frame_t *cfp) { if (!rb_zjit_enabled_p) return NULL; #if USE_ZJIT @@ -87,7 +87,7 @@ CFP_JIT_RETURN(const rb_control_frame_t *cfp) static inline const VALUE* CFP_PC(const rb_control_frame_t *cfp) { - if (CFP_JIT_RETURN(cfp)) { + if (CFP_ZJIT_FRAME(cfp)) { return ((const zjit_jit_frame_t *)cfp->jit_return)->pc; } return cfp->pc; @@ -96,7 +96,7 @@ CFP_PC(const rb_control_frame_t *cfp) static inline const rb_iseq_t* CFP_ISEQ(const rb_control_frame_t *cfp) { - if (CFP_JIT_RETURN(cfp)) { + if (CFP_ZJIT_FRAME(cfp)) { return ((const zjit_jit_frame_t *)cfp->jit_return)->iseq; } return cfp->_iseq; diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 7c8a3c758b1fbd..f92ed92eb8f253 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -5544,3 +5544,30 @@ fn test_send_block_to_method_not_using_block() { test "), @"42"); } + +#[test] +fn test_send_block_unused_warning_emitted_from_jit() { + // When ZJIT compiles a send with a block as a dynamic dispatch fallback + // (gen_send -> rb_vm_send), warn_unused_block uses cfp->pc for the dedup + // key. We save cfp->pc before calling rb_vm_send so the key is stable + // and won't spuriously collide with prior entries in the dedup table. + assert_snapshot!(inspect(r#" + $warnings = [] + module Warning + def warn(message, category: nil) + $warnings << message + end + end + + def m_unused_block_warn_test = 42 + + def test + $VERBOSE = true + m_unused_block_warn_test {} + $warnings.any? { |w| w.include?("may be ignored") } + end + + test + test + "#), @"true"); +} diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 3c00275478523c..38c8044f8f6f48 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1228,13 +1228,13 @@ pub mod test_utils { // "Fun" double pointer dance to get a thin function pointer to pass through C unsafe extern "C" fn callback_wrapper(data: VALUE) -> VALUE { // SAFETY: shorter lifetime than the data local in the caller frame - let callback: &mut &mut dyn FnMut() = unsafe { std::mem::transmute(data) }; - callback(); + let callback: *mut &mut dyn FnMut() = std::ptr::with_exposed_provenance_mut(data.0); + unsafe { (*callback)() }; Qnil } let mut state: c_int = 0; - unsafe { super::rb_protect(Some(callback_wrapper), VALUE((&mut data) as *mut _ as usize), &mut state) }; + unsafe { super::rb_protect(Some(callback_wrapper), VALUE((&raw mut data).expose_provenance()), &mut state) }; if state != 0 { unsafe { rb_zjit_print_exception(); } assert_eq!(0, state, "Exceptional unwind in callback. Ruby exception?");