From 65f2a4beecd3017ced366e3957eef501f2860c1f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:36:58 -0800 Subject: [PATCH 01/48] Add k0kubun as CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000000..d3650266bb602a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @k0kubun From cefa630bce3aea8317f09fe73c6439f67c42bb69 Mon Sep 17 00:00:00 2001 From: Nick Dower Date: Thu, 2 Jan 2025 17:24:55 +0100 Subject: [PATCH 02/48] YJIT: Add crashing test for yielding keyword args Code like the following is crashing for us on 3.4.1: ```ruby def a(&) = yield(x: 0) 1000.times { a { |x:| x } } ``` Crash: ``` ruby: YJIT has panicked. More info to follow... thread '' panicked at ./yjit/src/codegen.rs:8018:13: assertion `left == right` failed left: 0 right: 1 ``` Co-authored-by: Dani Acherkan --- bootstraptest/test_yjit.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 6a1aebccd76475..8c6171bd0a656f 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -37,27 +37,35 @@ def call(args) } # test discarding extra yield arguments -assert_equal "2210150001501015", %q{ +assert_equal "22131300500015901015", %q{ def splat_kw(ary) = yield *ary, a: 1 def splat(ary) = yield *ary - def kw = yield 1, 2, a: 0 + def kw = yield 1, 2, a: 3 + + def kw_only = yield a: 0 def simple = yield 0, 1 + def none = yield + def calls [ splat([1, 1, 2]) { |x, y| x + y }, splat([1, 1, 2]) { |y, opt = raise| opt + y}, splat_kw([0, 1]) { |a:| a }, kw { |a:| a }, - kw { |a| a }, + kw { |one| one }, + kw { |one, a:| a }, + kw_only { |a:| a }, + kw_only { |a: 1| a }, simple { 5.itself }, simple { |a| a }, simple { |opt = raise| opt }, simple { |*rest| rest }, simple { |opt_kw: 5| opt_kw }, + none { |a: 9| a }, # autosplat ineractions [0, 1, 2].yield_self { |a, b| [a, b] }, [0, 1, 2].yield_self { |a, opt = raise| [a, opt] }, From 73690b520da4c3ab680ddc477cacaeaeed75d558 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Sat, 4 Jan 2025 11:41:00 -0500 Subject: [PATCH 03/48] YJIT: Fix crash when yielding keyword arguments Previously, the code for dropping surplus arguments when yielding into blocks erroneously attempted to drop keyword arguments when there is in fact no surplus arguments. Fix the condition and test that supplying the exact number of keyword arguments as require compiles without fallback. --- test/ruby/test_yjit.rb | 8 ++++++++ yjit/src/codegen.rs | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 0c8ed691d099d4..0e476588f4954e 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -1742,6 +1742,14 @@ def each RUBY end + def test_yield_kwargs + assert_compiles(<<~RUBY, result: 3, no_send_fallbacks: true) + def req2kws = yield a: 1, b: 2 + + req2kws { |a:, b:| a + b } + RUBY + end + private def code_gc_helpers diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 37ddbce0bb2501..d04da48c6a0de1 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -8011,14 +8011,14 @@ fn gen_send_iseq( // Pop surplus positional arguments when yielding if arg_setup_block { - let extras = argc - required_num - opt_num; + let extras = argc - required_num - opt_num - kw_arg_num; if extras > 0 { // Checked earlier. If there are keyword args, then // the positional arguments are not at the stack top. assert_eq!(0, kw_arg_num); asm.stack_pop(extras as usize); - argc = required_num + opt_num; + argc = required_num + opt_num + kw_arg_num; } } From b5c921e01074891680b45353519a1e0bfe7c1036 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 14 Jan 2025 19:11:33 +0900 Subject: [PATCH 04/48] [Bug #21024] header has been useless And finally deprecated at C++-17. Patched by jprokop (Jarek Prokop). --- include/ruby/internal/stdbool.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/ruby/internal/stdbool.h b/include/ruby/internal/stdbool.h index 1ca61136ba2636..7f3e6dcf97cedd 100644 --- a/include/ruby/internal/stdbool.h +++ b/include/ruby/internal/stdbool.h @@ -27,10 +27,6 @@ #elif defined(__cplusplus) # /* bool is a keyword in C++. */ -# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) -# include -# endif -# # ifndef __bool_true_false_are_defined # define __bool_true_false_are_defined # endif From 3c9e65d370f43b8eec99debad606964f80a935c5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 6 Jan 2025 15:49:13 +0900 Subject: [PATCH 05/48] glibc and musl didn't provide A1-A7, FP and S3-S8 constants. * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/loongarch/sys/ucontext.h * https://git.musl-libc.org/cgit/musl/tree/arch/loongarch64/bits/signal.h --- vm_dump.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vm_dump.c b/vm_dump.c index 5873e529607246..99339fd9cc07bb 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -1001,23 +1001,23 @@ rb_dump_machine_register(FILE *errout, const ucontext_t *ctx) # elif defined __loongarch64 dump_machine_register(mctx->__gregs[LARCH_REG_SP], "sp"); dump_machine_register(mctx->__gregs[LARCH_REG_A0], "a0"); - dump_machine_register(mctx->__gregs[LARCH_REG_A1], "a1"); - dump_machine_register(mctx->__gregs[LARCH_REG_A2], "a2"); - dump_machine_register(mctx->__gregs[LARCH_REG_A3], "a3"); - dump_machine_register(mctx->__gregs[LARCH_REG_A4], "a4"); - dump_machine_register(mctx->__gregs[LARCH_REG_A5], "a5"); - dump_machine_register(mctx->__gregs[LARCH_REG_A6], "a6"); - dump_machine_register(mctx->__gregs[LARCH_REG_A7], "a7"); - dump_machine_register(mctx->__gregs[LARCH_REG_FP], "fp"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+1], "a1"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+2], "a2"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+3], "a3"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+4], "a4"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+5], "a5"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+6], "a6"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+7], "a7"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+8], "fp"); dump_machine_register(mctx->__gregs[LARCH_REG_S0], "s0"); dump_machine_register(mctx->__gregs[LARCH_REG_S1], "s1"); dump_machine_register(mctx->__gregs[LARCH_REG_S2], "s2"); - dump_machine_register(mctx->__gregs[LARCH_REG_S3], "s3"); - dump_machine_register(mctx->__gregs[LARCH_REG_S4], "s4"); - dump_machine_register(mctx->__gregs[LARCH_REG_S5], "s5"); - dump_machine_register(mctx->__gregs[LARCH_REG_S6], "s6"); - dump_machine_register(mctx->__gregs[LARCH_REG_S7], "s7"); - dump_machine_register(mctx->__gregs[LARCH_REG_S8], "s8"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+3], "s3"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+4], "s4"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+5], "s5"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+6], "s6"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+7], "s7"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+8], "s8"); # endif } # elif defined __APPLE__ From 555373aec859413efd3237cf717931f4b7ac1cb1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 5 Feb 2025 14:49:14 +0900 Subject: [PATCH 06/48] Bump up net-smtp-0.5.1 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 709d447c9cda30..d15ca4f72620a1 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -15,7 +15,7 @@ rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp net-imap 0.5.4 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop -net-smtp 0.5.0 https://github.com/ruby/net-smtp +net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.3 https://github.com/ruby/prime rbs 3.8.0 https://github.com/ruby/rbs From 787436cd1ae00616b88bc494baaaee7c361f31b9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Feb 2025 11:02:42 +0900 Subject: [PATCH 07/48] Support `git ls-files ...`.split style for file list of gemspec --- tool/rbinstall.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index ebd76dc2c950de..b194f2c9c26cc9 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -772,7 +772,7 @@ def load_gemspec(file, base = nil) next if File.directory?(File.join(base, n)) files << n.dump end if base - code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split\([^\)]*\)/m) do + code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split(\([^\)]*\))?/m) do "[" + files.join(", ") + "]" end code.gsub!(/IO\.popen\(.*git.*?\)/) do From 12c716eea02f0efbb7dcd4ddb3a8b0523cdb99c2 Mon Sep 17 00:00:00 2001 From: nick evans Date: Tue, 11 Feb 2025 09:03:02 -0500 Subject: [PATCH 08/48] Bump net-imap to v0.5.6 for Ruby 3.4 (CVE-2025-25186) This update addresses CVE-2025-25186 (GHSA-7fc5-f82f-cx69). --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index d15ca4f72620a1..3873767596a984 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -13,7 +13,7 @@ test-unit 3.6.7 https://github.com/test-unit/test-unit rexml 3.4.0 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp -net-imap 0.5.4 https://github.com/ruby/net-imap +net-imap 0.5.6 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix From 706f1d0573f0b807bee4c0cc8937b8f5b9b24ebd Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 28 Jan 2025 16:13:28 -0500 Subject: [PATCH 09/48] YJIT: Initialize locals in ISeqs defined with `...` Backport of GH-12660: Previously, callers of forwardable ISeqs moved the stack pointer up without writing to the stack. If there happens to be a stale value in the area skipped over, it could crash due to "try to mark T_NONE". Also, the uninitialized local variables were observable through `binding`. Initialize the locals to nil. [Bug #21021] --- bootstraptest/test_yjit.rb | 32 ++++++++++++++++++++++++++++++++ yjit/src/codegen.rs | 5 ++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 8c6171bd0a656f..34cf80fc7faa48 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -1,6 +1,38 @@ # To run the tests in this file only, with YJIT enabled: # make btest BTESTS=bootstraptest/test_yjit.rb RUN_OPTS="--yjit-call-threshold=1" +# This used to trigger a "try to mark T_NONE" +# due to an uninitialized local in foo. +assert_normal_exit %{ + def foo(...) + _local_that_should_nil_on_call = GC.start + end + + def test_bug21021 + puts [], [], [], [], [], [] + foo [] + end + + GC.stress = true + test_bug21021 +} + +assert_equal 'nil', %{ + def foo(...) + _a = _b = _c = binding.local_variable_get(:_c) + + _c + end + + # [Bug #21021] + def test_local_fill_in_forwardable + puts [], [], [], [], [] + foo [] + end + + test_local_fill_in_forwardable.inspect +} + # regression test for popping before side exit assert_equal "ok", %q{ def foo(a, *) = a diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index d04da48c6a0de1..cb97310992e91b 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -8068,7 +8068,6 @@ fn gen_send_iseq( } } - // Don't nil fill forwarding iseqs if !forwarding { // Nil-initialize missing optional parameters nil_fill( @@ -8103,9 +8102,13 @@ fn gen_send_iseq( assert_eq!(1, num_params); // Write the CI in to the stack and ensure that it actually gets // flushed to memory + asm_comment!(asm, "put call info for forwarding"); let ci_opnd = asm.stack_opnd(-1); asm.ctx.dealloc_reg(ci_opnd.reg_opnd()); asm.mov(ci_opnd, VALUE(ci as usize).into()); + + // Nil-initialize other locals which are above the CI + nil_fill("nil-initialize locals", 1..num_locals, asm); } // Points to the receiver operand on the stack unless a captured environment is used From 294aef53b8c988f605001e4555d9532cefe8806d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 8 Jan 2025 19:07:07 -0500 Subject: [PATCH 10/48] YJIT: Filter `&` calls from specialized C method codegen Evident with the crash reported in [Bug #20997], the C replacement codegen functions aren't authored to handle block arguments (nor should they because the extra code from the complexity defeats optimization). Filter sites with VM_CALL_ARGS_BLOCKARG. --- bootstraptest/test_yjit.rb | 8 ++++++++ yjit/src/codegen.rs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 34cf80fc7faa48..44128906926f58 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -5379,3 +5379,11 @@ def reverse_even Swap.new("xy").swap + Swap.new("cat").reverse_odd + Swap.new("abcd").reverse_even RUBY + +assert_normal_exit %{ + class Bug20997 + def foo(&) = self.class.name(&) + + new.foo + end +} diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index cb97310992e91b..a17069d38a0b4d 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -6894,11 +6894,12 @@ fn gen_send_cfunc( // Increment total cfunc send count gen_counter_incr(jit, asm, Counter::num_send_cfunc); - // Delegate to codegen for C methods if we have it. + // Delegate to codegen for C methods if we have it and the callsite is simple enough. if kw_arg.is_null() && !kw_splat && flags & VM_CALL_OPT_SEND == 0 && flags & VM_CALL_ARGS_SPLAT == 0 && + flags & VM_CALL_ARGS_BLOCKARG == 0 && (cfunc_argc == -1 || argc == cfunc_argc) { let expected_stack_after = asm.ctx.get_stack_size() as i32 - argc; if let Some(known_cfunc_codegen) = lookup_cfunc_codegen(unsafe { (*cme).def }) { From 4bafaef0a55fdc45a7b8275a82ceb0f3e3419d00 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:47:24 -0800 Subject: [PATCH 11/48] merge revision(s) 7df5d65eac86940619f87da7e70bc0911097ae2f: [Backport #20981] [Bug #20981] Bring back `rb_undefine_finalizer` --- gc.c | 6 ++++++ version.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gc.c b/gc.c index 1ec159a2dafae1..68304d26c50e0d 100644 --- a/gc.c +++ b/gc.c @@ -1618,6 +1618,12 @@ os_each_obj(int argc, VALUE *argv, VALUE os) static VALUE undefine_final(VALUE os, VALUE obj) +{ + return rb_undefine_finalizer(obj); +} + +VALUE +rb_undefine_finalizer(VALUE obj) { rb_check_frozen(obj); diff --git a/version.h b/version.h index 82dbaab0e0e93d..4f4d12a9d2e3a5 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 0 +#define RUBY_PATCHLEVEL 1 #include "ruby/version.h" #include "ruby/internal/abi.h" From 5ba052675f0eefe77dd7ab30bb9ac186f8d82ccb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:50:05 -0800 Subject: [PATCH 12/48] merge revision(s) fb18bb183c24ca82b8f114ed090d62bd69b5df84: [Backport #20989] [Bug #20989] Ripper: Pass `compile_error` For the universal parser, `rb_parser_reg_fragment_check` function is shared between the parser and ripper. However `parser_params` struct is partially different, and `compile_error` function depends on that part indirectly. --- internal/parse.h | 3 ++- parse.y | 17 ++++++++++++----- test/ripper/test_ripper.rb | 15 +++++++++++++++ version.h | 2 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/internal/parse.h b/internal/parse.h index c4df4b9377a3ee..8e04664ae8a7d2 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -78,7 +78,8 @@ void rb_parser_string_free(rb_parser_t *p, rb_parser_string_t *str); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); -int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); +typedef void (*rb_parser_reg_fragment_error_func)(struct parser_params *, VALUE); +int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int, rb_parser_reg_fragment_error_func); int rb_reg_named_capture_assign_iter_impl(struct parser_params *p, const char *s, long len, rb_encoding *enc, NODE **succ_block, const rb_code_location_t *loc, rb_parser_assignable_func assignable); int rb_parser_local_defined(struct parser_params *p, ID id, const struct rb_iseq_struct *iseq); NODE *rb_parser_assignable(struct parser_params *p, ID id, NODE *val, const YYLTYPE *loc); diff --git a/parse.y b/parse.y index cbce831c5bdda7..b238f8d6519c31 100644 --- a/parse.y +++ b/parse.y @@ -1480,8 +1480,9 @@ static rb_ast_id_table_t *local_tbl(struct parser_params*); static VALUE reg_compile(struct parser_params*, rb_parser_string_t*, int); static void reg_fragment_setenc(struct parser_params*, rb_parser_string_t*, int); -#define reg_fragment_check rb_parser_reg_fragment_check -int reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); +int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int, rb_parser_reg_fragment_error_func); +static void reg_fragment_error(struct parser_params *, VALUE); +#define reg_fragment_check(p, str, option) rb_parser_reg_fragment_check(p, str, option, reg_fragment_error) static int literal_concat0(struct parser_params *p, rb_parser_string_t *head, rb_parser_string_t *tail); static NODE *heredoc_dedent(struct parser_params*,NODE*); @@ -15378,9 +15379,15 @@ reg_fragment_setenc(struct parser_params* p, rb_parser_string_t *str, int option if (c) reg_fragment_enc_error(p, str, c); } +static void +reg_fragment_error(struct parser_params* p, VALUE err) +{ + compile_error(p, "%"PRIsVALUE, err); +} + #ifndef RIPPER int -reg_fragment_check(struct parser_params* p, rb_parser_string_t *str, int options) +rb_parser_reg_fragment_check(struct parser_params* p, rb_parser_string_t *str, int options, rb_parser_reg_fragment_error_func error) { VALUE err, str2; reg_fragment_setenc(p, str, options); @@ -15389,7 +15396,7 @@ reg_fragment_check(struct parser_params* p, rb_parser_string_t *str, int options err = rb_reg_check_preprocess(str2); if (err != Qnil) { err = rb_obj_as_string(err); - compile_error(p, "%"PRIsVALUE, err); + error(p, err); return 0; } return 1; @@ -15494,7 +15501,7 @@ reg_compile(struct parser_params* p, rb_parser_string_t *str, int options) if (NIL_P(re)) { VALUE m = rb_attr_get(rb_errinfo(), idMesg); rb_set_errinfo(err); - compile_error(p, "%"PRIsVALUE, m); + reg_fragment_error(p, m); return Qnil; } return re; diff --git a/test/ripper/test_ripper.rb b/test/ripper/test_ripper.rb index 0d3e33dde67aee..414ce83b7d8e8a 100644 --- a/test/ripper/test_ripper.rb +++ b/test/ripper/test_ripper.rb @@ -155,6 +155,18 @@ def test_assignable_in_regexp end; end + def test_invalid_multibyte_character_in_regexp + lex = Ripper.lex(%q[/#{"\xcd"}/]) + assert_equal([[1, 0], :on_regexp_beg, "/", state(:EXPR_BEG)], lex.shift) + assert_equal([[1, 1], :on_embexpr_beg, "\#{", state(:EXPR_BEG)], lex.shift) + assert_equal([[1, 3], :on_tstring_beg, "\"", state(:EXPR_BEG)], lex.shift) + assert_equal([[1, 4], :on_tstring_content, "\\xcd", state(:EXPR_BEG)], lex.shift) + assert_equal([[1, 8], :on_tstring_end, "\"", state(:EXPR_END)], lex.shift) + assert_equal([[1, 9], :on_embexpr_end, "}", state(:EXPR_END)], lex.shift) + assert_equal([[1, 10], :on_regexp_end, "/", state(:EXPR_BEG)], lex.shift) + assert_empty(lex) + end + def test_no_memory_leak assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) 2_000_000.times do @@ -202,4 +214,7 @@ def test_invalid_gets end end + def state(name) + Ripper::Lexer::State.new(Ripper.const_get(name)) + end end if ripper_test diff --git a/version.h b/version.h index 4f4d12a9d2e3a5..b2465417a4c90b 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 1 +#define RUBY_PATCHLEVEL 2 #include "ruby/version.h" #include "ruby/internal/abi.h" From d720ea59ca36f726b8484b3b66e3e2c40784ac89 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:51:03 -0800 Subject: [PATCH 13/48] merge revision(s) 2a1cff40f5e7875f69a7d0ed59eab92cc86c65ff: [Backport #21003] Do not warn unused block when using forwarding Fixes [Bug #21003] --- prism_compile.c | 2 ++ test/ruby/test_method.rb | 8 ++++++++ version.h | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/prism_compile.c b/prism_compile.c index bb3d91ffded6e1..a021b4c94f880e 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1756,6 +1756,8 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b break; } case PM_FORWARDING_ARGUMENTS_NODE: { // not counted in argc return value + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); + if (ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.forwardable) { *flags |= VM_CALL_FORWARDING; diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index 0707d61f1b87c7..5d5d5aac02fbde 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1776,5 +1776,13 @@ def f = nil RUBY assert_equal 0, err.size, err.join("\n") end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo(*, &block) = block + def bar(buz, ...) = foo(buz, ...) + bar(:test) {} # do not warn because of forwarding + RUBY + assert_equal 0, err.size, err.join("\n") + end end end diff --git a/version.h b/version.h index b2465417a4c90b..334b1ab7e73fb4 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 2 +#define RUBY_PATCHLEVEL 3 #include "ruby/version.h" #include "ruby/internal/abi.h" From 8a86e52fb5fc49367e79f8f49fb451ab74e089bb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:51:38 -0800 Subject: [PATCH 14/48] merge revision(s) 668701cb0b42f27c5f54309a358c6fcbb24b5d85: [Backport #21002] Added copyright for vendored turbo_tests. [Bug #21002] --- LEGAL | 27 +++++++++++++++++++++++++++ version.h | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/LEGAL b/LEGAL index 55c7ffc2917040..ee01de962b9a92 100644 --- a/LEGAL +++ b/LEGAL @@ -371,6 +371,33 @@ mentioned below. TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +[spec/lib/turbo_tests/*] +[spec/lib/turbo_tests.rb] +[spec/lib/utils/*] + + These files are under the MIT License. + + >>> + Copyright (c) 2020 Ilya Zub + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + [parse.c] [parse.h] diff --git a/version.h b/version.h index 334b1ab7e73fb4..1c6d0811ad9641 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 3 +#define RUBY_PATCHLEVEL 4 #include "ruby/version.h" #include "ruby/internal/abi.h" From 68013d43a35a8368caa7b30c501e270333d04b35 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:52:03 -0800 Subject: [PATCH 15/48] merge revision(s) 31905d9e23ec6d1fa2a52f1ef2533f2056e7c9fb: [Backport #21001] Allow escaping from ensures through next Fixes [Bug #21001] --- prism_compile.c | 9 --------- test/ruby/test_compile_prism.rb | 21 +++++++++++++++++++++ version.h | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index a021b4c94f880e..8bef6ce747fcbd 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -8716,16 +8716,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_ensure_node_t *cast = (const pm_ensure_node_t *) node; if (cast->statements != NULL) { - LABEL *start = NEW_LABEL(location.line); - LABEL *end = NEW_LABEL(location.line); - PUSH_LABEL(ret, start); - - LABEL *prev_end_label = ISEQ_COMPILE_DATA(iseq)->end_label; - ISEQ_COMPILE_DATA(iseq)->end_label = end; - PM_COMPILE((const pm_node_t *) cast->statements); - ISEQ_COMPILE_DATA(iseq)->end_label = prev_end_label; - PUSH_LABEL(ret, end); } return; diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index 043d5d8c0ec44a..33a8b17b990e50 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -1189,6 +1189,27 @@ def self.prism_test_ensure_node res RUBY + + # Bug #21001 + assert_prism_eval(<<~RUBY) + RUN_ARRAY = [1,2] + + MAP_PROC = Proc.new do |&blk| + block_results = [] + RUN_ARRAY.each do |value| + block_value = blk.call(value) + block_results.push block_value + end + block_results + ensure + next block_results + end + + MAP_PROC.call do |value| + break if value > 1 + next value + end + RUBY end def test_NextNode diff --git a/version.h b/version.h index 1c6d0811ad9641..63a909dec21996 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 4 +#define RUBY_PATCHLEVEL 5 #include "ruby/version.h" #include "ruby/internal/abi.h" From 82f7cb794109794f6ffa95ffb3be8e5149dd74bd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:52:28 -0800 Subject: [PATCH 16/48] merge revision(s) ad96c5a72908042489357b73dc936c4bc38d919b, ca81142eff98cccb03ff523322aefe4e7346fd0e: [Backport #21010] [ruby/prism] Throw syntax error for endless method with `[]=` Prism shoudld throw a syntax error for endless methods when the method name uses brackets. Previously it would not. This matches the behavior of parse.y. Fixes https://bugs.ruby-lang.org/issues/21010 https://github.com/ruby/prism/commit/43c16a89ef [ruby/prism] [Bug #21010] Reject endless method definition of `[]=` Fixes: https://bugs.ruby-lang.org/issues/20785 https://github.com/ruby/prism/commit/192960ce5d --- prism/prism.c | 5 +++-- test/prism/errors/defs_endless_method.txt | 12 ++++++++++++ ...ot_be_defined_in_an_endless_method_definition.txt | 3 +++ version.h | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 test/prism/errors/defs_endless_method.txt diff --git a/prism/prism.c b/prism/prism.c index 47106147d00513..9d21b25b7c3046 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1731,9 +1731,10 @@ char_is_global_name_punctuation(const uint8_t b) { static inline bool token_is_setter_name(pm_token_t *token) { return ( - (token->type == PM_TOKEN_IDENTIFIER) && + (token->type == PM_TOKEN_BRACKET_LEFT_RIGHT_EQUAL) || + ((token->type == PM_TOKEN_IDENTIFIER) && (token->end - token->start >= 2) && - (token->end[-1] == '=') + (token->end[-1] == '=')) ); } diff --git a/test/prism/errors/defs_endless_method.txt b/test/prism/errors/defs_endless_method.txt new file mode 100644 index 00000000000000..80db648e62750b --- /dev/null +++ b/test/prism/errors/defs_endless_method.txt @@ -0,0 +1,12 @@ +def f=(k,v)=1 + ^~ invalid method name; a setter method cannot be defined in an endless method definition + +def obj.f=(k,v)=1 + ^~ invalid method name; a setter method cannot be defined in an endless method definition + +def []=(k,v)=1 + ^~~ invalid method name; a setter method cannot be defined in an endless method definition + +def obj.[]=(k,v)=1 + ^~~ invalid method name; a setter method cannot be defined in an endless method definition + diff --git a/test/prism/errors/setter_method_cannot_be_defined_in_an_endless_method_definition.txt b/test/prism/errors/setter_method_cannot_be_defined_in_an_endless_method_definition.txt index c4440ccc7ec10f..7927664f3c12ba 100644 --- a/test/prism/errors/setter_method_cannot_be_defined_in_an_endless_method_definition.txt +++ b/test/prism/errors/setter_method_cannot_be_defined_in_an_endless_method_definition.txt @@ -1,3 +1,6 @@ def a=() = 42 ^~ invalid method name; a setter method cannot be defined in an endless method definition +def []=() = 42 + ^~~ invalid method name; a setter method cannot be defined in an endless method definition + diff --git a/version.h b/version.h index 63a909dec21996..c7e4308eb87eb7 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 5 +#define RUBY_PATCHLEVEL 6 #include "ruby/version.h" #include "ruby/internal/abi.h" From 698f808cc78b01bc23ff893eaabcc09a454b56ee Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:53:11 -0800 Subject: [PATCH 17/48] merge revision(s) 5fec9308320e8b377681ef19b0cd46d53f94e8ac, 1a06bee027d5c5b65ed0aaee76ee0040554d4efd: [Backport #20992] [Bug #20992] Test for local variable name encodings Do not intern invalid symbols in eval parse When the inner code cannot represent the name of the locals in the outer code, do not bother putting them into the constant pool as they will not be referenced. Fixes [Bug #20992] Co-authored-by: Nobuyoshi Nakada --- test/ruby/test_variable.rb | 7 +++++++ version.h | 2 +- vm_eval.c | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index 86f2e4bb84d3e9..49fec2d40e9e5e 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -425,6 +425,13 @@ def test_many_instance_variables end end + def test_local_variables_encoding + α = 1 + b = binding + b.eval("".encode("us-ascii")) + assert_equal(%i[α b], b.local_variables) + end + private def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:) local_variables diff --git a/version.h b/version.h index c7e4308eb87eb7..00e4d7f43f8c34 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 6 +#define RUBY_PATCHLEVEL 7 #include "ruby/version.h" #include "ruby/internal/abi.h" diff --git a/vm_eval.c b/vm_eval.c index eaf59f41a389da..a8c12707ec3e65 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1690,6 +1690,8 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, // scopes array refer to root nodes on the tree, and higher indexes are the // leaf nodes. iseq = parent; + rb_encoding *encoding = rb_enc_get(src); + for (int scopes_index = 0; scopes_index < scopes_count; scopes_index++) { VALUE iseq_value = (VALUE)iseq; int locals_count = ISEQ_BODY(iseq)->local_table_size; @@ -1711,6 +1713,14 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, continue; } + // Check here if this local can be represented validly in the + // encoding of the source string. If it _cannot_, then it should + // not be added to the constant pool as it would not be able to + // be referenced anyway. + if (rb_enc_str_coderange_scan(name_obj, encoding) == ENC_CODERANGE_BROKEN) { + continue; + } + /* We need to duplicate the string because the Ruby string may * be embedded so compaction could move the string and the pointer * will change. */ From c3d30e4dabb2b895c347e10221467dd1b1124ca4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:53:47 -0800 Subject: [PATCH 18/48] merge revision(s) 051af9529b03c11aff8f6a09522c69983e3a9d72: [Backport #21017] [Bug #21017] Fix `--with-parser` configure option --- .github/workflows/parsey.yml | 2 ++ configure.ac | 4 ++-- version.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/parsey.yml b/.github/workflows/parsey.yml index a7416952b6d49e..3d5e314eaf6e2c 100644 --- a/.github/workflows/parsey.yml +++ b/.github/workflows/parsey.yml @@ -73,6 +73,8 @@ jobs: - run: make + - run: make TESTRUN_SCRIPT='-e "exit !RUBY_DESCRIPTION.include?(%[+PRISM])"' run + - name: make ${{ matrix.test_task }} run: make -s ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" SPECOPTS="$SPECOPTS" env: diff --git a/configure.ac b/configure.ac index 30e197bf973448..5899f2aa9c88f6 100644 --- a/configure.ac +++ b/configure.ac @@ -4547,10 +4547,10 @@ AS_IF([test x"$enable_rubygems" = xno], [ AC_SUBST(USE_RUBYGEMS) m4_define(available_parsers, [parse.y, prism]) -with_parser=prism AC_ARG_WITH(parser, AS_HELP_STRING([--with-parser=PARSER], - [specify default parser; PARSER is one of ]m4_join([, ],available_parsers))) + [specify default parser; PARSER is one of ]m4_join([, ],available_parsers)), + [], [with_parser=prism]) AS_CASE([$with_parser], m4_foreach(parser, [available_parsers], parser[,][AC_DEFINE_UNQUOTED(RB_DEFAULT_PARSER, RB_DEFAULT_PARSER_[]AS_TR_CPP(parser)),]) diff --git a/version.h b/version.h index 00e4d7f43f8c34..3899ed1acadfeb 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 7 +#define RUBY_PATCHLEVEL 8 #include "ruby/version.h" #include "ruby/internal/abi.h" From 9240f01b1fbe5ea4f6a2017d35f8f605f84f0039 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:54:21 -0800 Subject: [PATCH 19/48] merge revision(s) 63723c8d5908569918fb27e070ae5bc9de33c8bd: [Backport #21014] Correctly set node_id on iseq location The iseq location object has a slot for node ids. parse.y was correctly populating that field but Prism was not. This commit populates the field with the ast node id for that iseq [Bug #21014] --- iseq.c | 2 +- prism_compile.c | 4 ++-- test/ruby/test_compile_prism.rb | 6 ++++++ version.h | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/iseq.c b/iseq.c index 639ca3a4cd014e..72769f26acadef 100644 --- a/iseq.c +++ b/iseq.c @@ -1072,7 +1072,7 @@ pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpa .end_pos = { .lineno = (int) end.line, .column = (int) end.column } }; - prepare_iseq_build(iseq, name, path, realpath, first_lineno, &code_location, -1, + prepare_iseq_build(iseq, name, path, realpath, first_lineno, &code_location, node->ast_node->node_id, parent, isolated_depth, type, node->script_lines == NULL ? Qnil : *node->script_lines, option); struct pm_iseq_new_with_opt_data data = { diff --git a/prism_compile.c b/prism_compile.c index 8bef6ce747fcbd..a63bf490f59611 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6482,7 +6482,7 @@ pm_compile_scope_node(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_nod case ISEQ_TYPE_BLOCK: { LABEL *start = ISEQ_COMPILE_DATA(iseq)->start_label = NEW_LABEL(0); LABEL *end = ISEQ_COMPILE_DATA(iseq)->end_label = NEW_LABEL(0); - const pm_node_location_t block_location = { .line = body->location.first_lineno, .node_id = -1 }; + const pm_node_location_t block_location = { .line = body->location.first_lineno, .node_id = scope_node->ast_node->node_id }; start->rescued = LABEL_RESCUE_BEG; end->rescued = LABEL_RESCUE_END; @@ -6608,7 +6608,7 @@ pm_compile_scope_node(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_nod } if (!PM_NODE_TYPE_P(scope_node->ast_node, PM_ENSURE_NODE)) { - const pm_node_location_t location = { .line = ISEQ_COMPILE_DATA(iseq)->last_line, .node_id = -1 }; + const pm_node_location_t location = { .line = ISEQ_COMPILE_DATA(iseq)->last_line, .node_id = scope_node->ast_node->node_id }; PUSH_INSN(ret, location, leave); } } diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index 33a8b17b990e50..a2e8b7bd97d64d 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -3,6 +3,12 @@ # This file is organized to match itemization in https://github.com/ruby/prism/issues/1335 module Prism class TestCompilePrism < Test::Unit::TestCase + def test_iseq_has_node_id + code = "proc { <, -1 + end + # Subclass is used for tests which need it class Subclass; end ############################################################################ diff --git a/version.h b/version.h index 3899ed1acadfeb..ead21823454c40 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 8 +#define RUBY_PATCHLEVEL 9 #include "ruby/version.h" #include "ruby/internal/abi.h" From c989d90754edeefa4e692d2cd8c351394cb217e7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 17:54:41 -0800 Subject: [PATCH 20/48] merge revision(s) 117d6e145a0270ab8fc9134403519ef13b9ebb24: [Backport #21027] [ruby/prism] Fix `not` receiver `not foo` should be `!foo` `not()` should be `!nil` Fixes [Bug #21027] https://github.com/ruby/prism/commit/871ed4b462 --- lib/prism/translation/ripper.rb | 16 +++++++++++++--- prism/prism.c | 5 +++-- version.h | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 018842715b0be0..dce96e01ab4346 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1045,10 +1045,20 @@ def visit_call_node(node) bounds(node.location) on_unary(node.name, receiver) when :! - receiver = visit(node.receiver) + if node.message == "not" + receiver = + if !node.receiver.is_a?(ParenthesesNode) || !node.receiver.body.nil? + visit(node.receiver) + end - bounds(node.location) - on_unary(node.message == "not" ? :not : :!, receiver) + bounds(node.location) + on_unary(:not, receiver) + else + receiver = visit(node.receiver) + + bounds(node.location) + on_unary(:!, receiver) + end when *BINARY_OPERATORS receiver = visit(node.receiver) value = visit(node.arguments.arguments.first) diff --git a/prism/prism.c b/prism/prism.c index 9d21b25b7c3046..8c46e31d308a7c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19711,11 +19711,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b accept1(parser, PM_TOKEN_NEWLINE); if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { - arguments.opening_loc = PM_LOCATION_TOKEN_VALUE(&parser->previous); + pm_token_t lparen = parser->previous; if (accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { - arguments.closing_loc = PM_LOCATION_TOKEN_VALUE(&parser->previous); + receiver = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, NULL, &parser->previous); } else { + arguments.opening_loc = PM_LOCATION_TOKEN_VALUE(&lparen); receiver = parse_expression(parser, PM_BINDING_POWER_COMPOSITION, true, false, PM_ERR_NOT_EXPRESSION, (uint16_t) (depth + 1)); if (!parser->recovering) { diff --git a/version.h b/version.h index ead21823454c40..82f505e172dfd6 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 9 +#define RUBY_PATCHLEVEL 10 #include "ruby/version.h" #include "ruby/internal/abi.h" From b65cea74295358265dfabc9e1f4d107b21e58e58 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 18:01:20 -0800 Subject: [PATCH 21/48] merge revision(s) 8034e9c3d001ca3dff124ab42972684eac8af2ae: [Backport #20995] [Bug #20995] Protect `IO.popen` block from exiting by exception --- io.c | 2 +- test/ruby/test_process.rb | 30 ++++++++++++++++++++++-------- version.h | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/io.c b/io.c index eb56094296eaab..95282b033712f8 100644 --- a/io.c +++ b/io.c @@ -8038,7 +8038,7 @@ popen_finish(VALUE port, VALUE klass) if (NIL_P(port)) { /* child */ if (rb_block_given_p()) { - rb_yield(Qnil); + rb_protect(rb_yield, Qnil, NULL); rb_io_flush(rb_ractor_stdout()); rb_io_flush(rb_ractor_stderr()); _exit(0); diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 6e8ba484a49ac9..af72053234f4b7 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -922,15 +922,29 @@ def test_execopts_popen_extra_fd } end - def test_popen_fork - IO.popen("-") {|io| - if !io - puts "fooo" - else - assert_equal("fooo\n", io.read) + if Process.respond_to?(:fork) + def test_popen_fork + IO.popen("-") do |io| + if !io + puts "fooo" + else + assert_equal("fooo\n", io.read) + end end - } - rescue NotImplementedError + end + + def test_popen_fork_ensure + IO.popen("-") do |io| + if !io + STDERR.reopen(STDOUT) + raise "fooo" + else + assert_empty io.read + end + end + rescue RuntimeError + abort "[Bug #20995] should not reach here" + end end def test_fd_inheritance diff --git a/version.h b/version.h index 82f505e172dfd6..8954d0897d33ed 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 10 +#define RUBY_PATCHLEVEL 11 #include "ruby/version.h" #include "ruby/internal/abi.h" From bc359b9971088ff921e26346f395c70640654e9e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 18:01:42 -0800 Subject: [PATCH 22/48] merge revision(s) b176d4f52e4af67654814dab3e9c5f4bf9170e54: [Backport #21008] [Bug #21008] Normalize before sum to float After switching to `Float`-mode when summing `Numeric` objects, normalization for `Float` is still needed. --- enum.c | 2 +- test/ruby/test_enumerator.rb | 15 +++++++++++++++ version.h | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/enum.c b/enum.c index d8a7cb73f3f707..6e4710fbcb6f3e 100644 --- a/enum.c +++ b/enum.c @@ -4704,7 +4704,7 @@ sum_iter(VALUE i, struct enum_sum_memo *memo) } else switch (TYPE(memo->v)) { default: sum_iter_some_value(i, memo); return; - case T_FLOAT: sum_iter_Kahan_Babuska(i, memo); return; + case T_FLOAT: case T_FIXNUM: case T_BIGNUM: case T_RATIONAL: diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 7599d434635d19..cd62cd8acb7746 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -1043,4 +1043,19 @@ def test_freeze assert_raise(FrozenError) { e.feed 1 } assert_raise(FrozenError) { e.rewind } end + + def test_sum_of_numeric + num = Class.new(Numeric) do + attr_reader :to_f + def initialize(val) + @to_f = Float(val) + end + end + + ary = [5, 10, 20].map {|i| num.new(i)} + + assert_equal(35.0, ary.sum) + enum = ary.each + assert_equal(35.0, enum.sum) + end end diff --git a/version.h b/version.h index 8954d0897d33ed..1b55a387866a6e 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 11 +#define RUBY_PATCHLEVEL 12 #include "ruby/version.h" #include "ruby/internal/abi.h" From 579b94ae61d123dfb06caa2499be4ef6879cbf1f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:06:07 -0800 Subject: [PATCH 23/48] merge revision(s) 931a870606f4e286a1c7bacf022940994f3c431b: [Backport #21044] [ruby/prism] Increase value of PRISM_DEPTH_MAXIMUM to 10000 The previous value of 1_000 was added with a reference to the Bison parser[^1], but the value of YYMAXDEPTH in the Bison docs is 10_000, not 1_000. [^1]: https://www.gnu.org/software/bison/manual/html_node/Memory-Management.html Fixes [Bug #21044] https://github.com/ruby/prism/commit/e098533ab4 Co-authored-by: Nony Dutton --- prism/defines.h | 2 +- version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prism/defines.h b/prism/defines.h index f7bb2120c431e1..0e67ff5221c6b0 100644 --- a/prism/defines.h +++ b/prism/defines.h @@ -31,7 +31,7 @@ * specifying a maximum depth to which we are allowed to recurse. */ #ifndef PRISM_DEPTH_MAXIMUM - #define PRISM_DEPTH_MAXIMUM 1000 + #define PRISM_DEPTH_MAXIMUM 10000 #endif /** diff --git a/version.h b/version.h index 1b55a387866a6e..37ee5354734b51 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 12 +#define RUBY_PATCHLEVEL 13 #include "ruby/version.h" #include "ruby/internal/abi.h" From 7adf89d7ad30552d7e57709d24eec266f601d38b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:06:50 -0800 Subject: [PATCH 24/48] merge revision(s) cb419e3912f0514b8151469b0a4a4b83cbbcce78: [Backport #21031] [PRISM] Handle forwarding inside eval Fixes [Bug #21031] --- prism_compile.c | 6 ---- prism_compile.h | 6 ++++ test/ruby/test_syntax.rb | 10 ++++++ version.h | 2 +- vm_eval.c | 72 ++++++++++++++++++++++++++++++++++++---- 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index a63bf490f59611..362aecfbb046a4 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -138,12 +138,6 @@ pm_iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int node #define PM_COMPILE_NOT_POPPED(node) \ pm_compile_node(iseq, (node), ret, false, scope_node) -#define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) -#define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) - #define PM_NODE_START_LOCATION(parser, node) \ ((pm_node_location_t) { .line = pm_newline_list_line(&(parser)->newline_list, ((const pm_node_t *) (node))->location.start, (parser)->start_line), .node_id = ((const pm_node_t *) (node))->node_id }) diff --git a/prism_compile.h b/prism_compile.h index f18fdbf892a9a8..c032449bd65ca9 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -83,6 +83,12 @@ typedef struct { bool parsed; } pm_parse_result_t; +#define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) +#define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) + VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath, bool load_error); VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index a226b10d947cac..62f1d99bdc855f 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -139,6 +139,11 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** inner(&) end assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10}) + + def evaled(&) + eval("inner(&)") + end + assert_equal(1, evaled{1}) end; end @@ -156,8 +161,10 @@ def test_anonymous_rest_forwarding def b(*); c(*) end def c(*a); a end def d(*); b(*, *) end + def e(*); eval("b(*)") end assert_equal([1, 2], b(1, 2)) assert_equal([1, 2, 1, 2], d(1, 2)) + assert_equal([1, 2], e(1, 2)) end; end @@ -177,10 +184,12 @@ def c(**kw); kw end def d(**); b(k: 1, **) end def e(**); b(**, k: 1) end def f(a: nil, **); b(**) end + def g(**); eval("b(**)") end assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) assert_equal({k: 3}, f(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, g(a: 1, k: 3)) end; end @@ -2010,6 +2019,7 @@ def obj1.bar(*args, **kws, &block) obj4 = obj1.clone obj5 = 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__) diff --git a/version.h b/version.h index 37ee5354734b51..279b68f1bac747 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 13 +#define RUBY_PATCHLEVEL 14 #include "ruby/version.h" #include "ruby/internal/abi.h" diff --git a/vm_eval.c b/vm_eval.c index a8c12707ec3e65..74dfd40907dbfa 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1692,12 +1692,24 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, iseq = parent; rb_encoding *encoding = rb_enc_get(src); +#define FORWARDING_POSITIONALS_CHR '*' +#define FORWARDING_POSITIONALS_STR "*" +#define FORWARDING_KEYWORDS_CHR ':' +#define FORWARDING_KEYWORDS_STR ":" +#define FORWARDING_BLOCK_CHR '&' +#define FORWARDING_BLOCK_STR "&" +#define FORWARDING_ALL_CHR '.' +#define FORWARDING_ALL_STR "." + for (int scopes_index = 0; scopes_index < scopes_count; scopes_index++) { VALUE iseq_value = (VALUE)iseq; int locals_count = ISEQ_BODY(iseq)->local_table_size; + pm_options_scope_t *options_scope = &result.options.scopes[scopes_count - scopes_index - 1]; pm_options_scope_init(options_scope, locals_count); + uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; + for (int local_index = 0; local_index < locals_count; local_index++) { pm_string_t *scope_local = &options_scope->locals[local_index]; ID local = ISEQ_BODY(iseq)->local_table[local_index]; @@ -1729,10 +1741,23 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, RB_GC_GUARD(name_obj); - pm_string_owned_init(scope_local, (uint8_t *)name_dup, length); + pm_string_owned_init(scope_local, (uint8_t *) name_dup, length); + } else if (local == idMULT) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS; + pm_string_constant_init(scope_local, FORWARDING_POSITIONALS_STR, 1); + } else if (local == idPow) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS; + pm_string_constant_init(scope_local, FORWARDING_KEYWORDS_STR, 1); + } else if (local == idAnd) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK; + pm_string_constant_init(scope_local, FORWARDING_BLOCK_STR, 1); + } else if (local == idDot3) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL; + pm_string_constant_init(scope_local, FORWARDING_ALL_STR, 1); } } + pm_options_scope_forwarding_set(options_scope, forwarding); iseq = ISEQ_BODY(iseq)->parent_iseq; /* We need to GC guard the iseq because the code above malloc memory @@ -1775,14 +1800,38 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, for (int local_index = 0; local_index < locals_count; local_index++) { const pm_string_t *scope_local = &options_scope->locals[local_index]; - pm_constant_id_t constant_id = 0; - if (pm_string_length(scope_local) > 0) { - constant_id = pm_constant_pool_insert_constant( - &result.parser.constant_pool, pm_string_source(scope_local), - pm_string_length(scope_local)); - st_insert(parent_scope->index_lookup_table, (st_data_t)constant_id, (st_data_t)local_index); + + const uint8_t *source = pm_string_source(scope_local); + size_t length = pm_string_length(scope_local); + + if (length > 0) { + if (length == 1) { + switch (*source) { + case FORWARDING_POSITIONALS_CHR: + constant_id = PM_CONSTANT_MULT; + break; + case FORWARDING_KEYWORDS_CHR: + constant_id = PM_CONSTANT_POW; + break; + case FORWARDING_BLOCK_CHR: + constant_id = PM_CONSTANT_AND; + break; + case FORWARDING_ALL_CHR: + constant_id = PM_CONSTANT_DOT3; + break; + default: + constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); + break; + } + } + else { + constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); + } + + st_insert(parent_scope->index_lookup_table, (st_data_t) constant_id, (st_data_t) local_index); } + pm_constant_id_list_append(&parent_scope->locals, constant_id); } @@ -1791,6 +1840,15 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, iseq = ISEQ_BODY(iseq)->parent_iseq; } +#undef FORWARDING_POSITIONALS_CHR +#undef FORWARDING_POSITIONALS_STR +#undef FORWARDING_KEYWORDS_CHR +#undef FORWARDING_KEYWORDS_STR +#undef FORWARDING_BLOCK_CHR +#undef FORWARDING_BLOCK_STR +#undef FORWARDING_ALL_CHR +#undef FORWARDING_ALL_STR + int error_state; iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, 0, &error_state); From cd395232b0491f9c4463c5889bab8d2c050e75e2 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:07:25 -0800 Subject: [PATCH 25/48] merge revision(s) 241ada7b1ca4fd71dc47a83d912ee25162a555d9: [Backport #21085] [ruby/prism] Do not put empty statements in while because of -n Fixes [Bug #21085] https://github.com/ruby/prism/commit/ebb9c36a10 --- prism/prism.c | 27 +++++++++++++++++---------- version.h | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 8c46e31d308a7c..a5adab9d8640a9 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22036,6 +22036,10 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc static pm_statements_node_t * wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { if (PM_PARSER_COMMAND_LINE_OPTION_P(parser)) { + if (statements == NULL) { + statements = pm_statements_node_create(parser); + } + pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, @@ -22051,6 +22055,10 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { if (PM_PARSER_COMMAND_LINE_OPTION_N(parser)) { if (PM_PARSER_COMMAND_LINE_OPTION_A(parser)) { + if (statements == NULL) { + statements = pm_statements_node_create(parser); + } + pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, @@ -22119,9 +22127,7 @@ parse_program(pm_parser_t *parser) { parser_lex(parser); pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_MAIN, 0); - if (statements == NULL) { - statements = pm_statements_node_create(parser); - } else if (!parser->parsing_eval) { + if (statements != NULL && !parser->parsing_eval) { // If we have statements, then the top-level statement should be // explicitly checked as well. We have to do this here because // everywhere else we check all but the last statement. @@ -22133,13 +22139,6 @@ parse_program(pm_parser_t *parser) { pm_locals_order(parser, &parser->current_scope->locals, &locals, true); pm_parser_scope_pop(parser); - // If this is an empty file, then we're still going to parse all of the - // statements in order to gather up all of the comments and such. Here we'll - // correct the location information. - if (pm_statements_node_body_length(statements) == 0) { - pm_statements_node_location_set(statements, parser->start, parser->start); - } - // At the top level, see if we need to wrap the statements in a program // node with a while loop based on the options. if (parser->command_line & (PM_OPTIONS_COMMAND_LINE_P | PM_OPTIONS_COMMAND_LINE_N)) { @@ -22149,6 +22148,14 @@ parse_program(pm_parser_t *parser) { pm_node_list_free(¤t_block_exits); } + // If this is an empty file, then we're still going to parse all of the + // statements in order to gather up all of the comments and such. Here we'll + // correct the location information. + if (statements == NULL) { + statements = pm_statements_node_create(parser); + pm_statements_node_location_set(statements, parser->start, parser->start); + } + return (pm_node_t *) pm_program_node_create(parser, &locals, statements); } diff --git a/version.h b/version.h index 279b68f1bac747..c6c3d1a3e6b7ec 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 14 +#define RUBY_PATCHLEVEL 15 #include "ruby/version.h" #include "ruby/internal/abi.h" From 04298f2d158fa860293d2ed2779cba69bc47f404 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:07:57 -0800 Subject: [PATCH 26/48] merge revision(s) c2908613368b2ae404d094a15df61d830fc46dc9: [Backport #21048] [ruby/prism] Fix rescue modifier precedence Fixes [Bug #21048] https://github.com/ruby/prism/commit/07202005cb --- prism/prism.c | 8 +- test/prism/fixtures/rescue_modifier.txt | 7 + test/prism/snapshots/rescue_modifier.txt | 230 +++++++++++++++++++++++ version.h | 2 +- 4 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 test/prism/fixtures/rescue_modifier.txt create mode 100644 test/prism/snapshots/rescue_modifier.txt diff --git a/prism/prism.c b/prism/prism.c index a5adab9d8640a9..8e30f096297f2c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -12966,7 +12966,7 @@ typedef struct { pm_binding_powers_t pm_binding_powers[PM_TOKEN_MAXIMUM] = { // rescue - [PM_TOKEN_KEYWORD_RESCUE_MODIFIER] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_MODIFIER_RESCUE), + [PM_TOKEN_KEYWORD_RESCUE_MODIFIER] = { PM_BINDING_POWER_MODIFIER_RESCUE, PM_BINDING_POWER_COMPOSITION, true, false }, // if unless until while [PM_TOKEN_KEYWORD_IF_MODIFIER] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_MODIFIER), @@ -19468,7 +19468,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); pm_token_t rescue_keyword = parser->previous; - pm_node_t *value = parse_expression(parser, binding_power, false, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + pm_node_t *value = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, false, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); statement = (pm_node_t *) pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value); @@ -20689,7 +20689,7 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_ pm_token_t rescue = parser->current; parser_lex(parser); - pm_node_t *right = parse_expression(parser, binding_power, false, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, false, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right); @@ -20795,7 +20795,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding } } - pm_node_t *right = parse_expression(parser, binding_power, accepts_command_call_inner, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, accepts_command_call_inner, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right); diff --git a/test/prism/fixtures/rescue_modifier.txt b/test/prism/fixtures/rescue_modifier.txt new file mode 100644 index 00000000000000..def9e2dbedef93 --- /dev/null +++ b/test/prism/fixtures/rescue_modifier.txt @@ -0,0 +1,7 @@ +a rescue b if c + +a = b rescue c if d + +a, = b rescue c if d + +def a = b rescue c if d diff --git a/test/prism/snapshots/rescue_modifier.txt b/test/prism/snapshots/rescue_modifier.txt new file mode 100644 index 00000000000000..0a27a3bb49cda7 --- /dev/null +++ b/test/prism/snapshots/rescue_modifier.txt @@ -0,0 +1,230 @@ +@ ProgramNode (location: (1,0)-(7,23)) +├── flags: ∅ +├── locals: [:a] +└── statements: + @ StatementsNode (location: (1,0)-(7,23)) + ├── flags: ∅ + └── body: (length: 4) + ├── @ IfNode (location: (1,0)-(1,15)) + │ ├── flags: newline + │ ├── if_keyword_loc: (1,11)-(1,13) = "if" + │ ├── predicate: + │ │ @ CallNode (location: (1,14)-(1,15)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :c + │ │ ├── message_loc: (1,14)-(1,15) = "c" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── then_keyword_loc: ∅ + │ ├── statements: + │ │ @ StatementsNode (location: (1,0)-(1,10)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ RescueModifierNode (location: (1,0)-(1,10)) + │ │ ├── flags: newline + │ │ ├── expression: + │ │ │ @ CallNode (location: (1,0)-(1,1)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (1,0)-(1,1) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (1,2)-(1,8) = "rescue" + │ │ └── rescue_expression: + │ │ @ CallNode (location: (1,9)-(1,10)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :b + │ │ ├── message_loc: (1,9)-(1,10) = "b" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── subsequent: ∅ + │ └── end_keyword_loc: ∅ + ├── @ IfNode (location: (3,0)-(3,19)) + │ ├── flags: newline + │ ├── if_keyword_loc: (3,15)-(3,17) = "if" + │ ├── predicate: + │ │ @ CallNode (location: (3,18)-(3,19)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :d + │ │ ├── message_loc: (3,18)-(3,19) = "d" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── then_keyword_loc: ∅ + │ ├── statements: + │ │ @ StatementsNode (location: (3,0)-(3,14)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ LocalVariableWriteNode (location: (3,0)-(3,14)) + │ │ ├── flags: newline + │ │ ├── name: :a + │ │ ├── depth: 0 + │ │ ├── name_loc: (3,0)-(3,1) = "a" + │ │ ├── value: + │ │ │ @ RescueModifierNode (location: (3,4)-(3,14)) + │ │ │ ├── flags: ∅ + │ │ │ ├── expression: + │ │ │ │ @ CallNode (location: (3,4)-(3,5)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ ├── message_loc: (3,4)-(3,5) = "b" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── keyword_loc: (3,6)-(3,12) = "rescue" + │ │ │ └── rescue_expression: + │ │ │ @ CallNode (location: (3,13)-(3,14)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :c + │ │ │ ├── message_loc: (3,13)-(3,14) = "c" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ └── operator_loc: (3,2)-(3,3) = "=" + │ ├── subsequent: ∅ + │ └── end_keyword_loc: ∅ + ├── @ IfNode (location: (5,0)-(5,20)) + │ ├── flags: newline + │ ├── if_keyword_loc: (5,16)-(5,18) = "if" + │ ├── predicate: + │ │ @ CallNode (location: (5,19)-(5,20)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :d + │ │ ├── message_loc: (5,19)-(5,20) = "d" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── then_keyword_loc: ∅ + │ ├── statements: + │ │ @ StatementsNode (location: (5,0)-(5,15)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ MultiWriteNode (location: (5,0)-(5,15)) + │ │ ├── flags: newline + │ │ ├── lefts: (length: 1) + │ │ │ └── @ LocalVariableTargetNode (location: (5,0)-(5,1)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :a + │ │ │ └── depth: 0 + │ │ ├── rest: + │ │ │ @ ImplicitRestNode (location: (5,1)-(5,2)) + │ │ │ └── flags: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (5,3)-(5,4) = "=" + │ │ └── value: + │ │ @ RescueModifierNode (location: (5,5)-(5,15)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (5,5)-(5,6)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :b + │ │ │ ├── message_loc: (5,5)-(5,6) = "b" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (5,7)-(5,13) = "rescue" + │ │ └── rescue_expression: + │ │ @ CallNode (location: (5,14)-(5,15)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :c + │ │ ├── message_loc: (5,14)-(5,15) = "c" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── subsequent: ∅ + │ └── end_keyword_loc: ∅ + └── @ IfNode (location: (7,0)-(7,23)) + ├── flags: newline + ├── if_keyword_loc: (7,19)-(7,21) = "if" + ├── predicate: + │ @ CallNode (location: (7,22)-(7,23)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :d + │ ├── message_loc: (7,22)-(7,23) = "d" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ └── block: ∅ + ├── then_keyword_loc: ∅ + ├── statements: + │ @ StatementsNode (location: (7,0)-(7,18)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ DefNode (location: (7,0)-(7,18)) + │ ├── flags: newline + │ ├── name: :a + │ ├── name_loc: (7,4)-(7,5) = "a" + │ ├── receiver: ∅ + │ ├── parameters: ∅ + │ ├── body: + │ │ @ StatementsNode (location: (7,8)-(7,18)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ RescueModifierNode (location: (7,8)-(7,18)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (7,8)-(7,9)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :b + │ │ │ ├── message_loc: (7,8)-(7,9) = "b" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (7,10)-(7,16) = "rescue" + │ │ └── rescue_expression: + │ │ @ CallNode (location: (7,17)-(7,18)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :c + │ │ ├── message_loc: (7,17)-(7,18) = "c" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── locals: [] + │ ├── def_keyword_loc: (7,0)-(7,3) = "def" + │ ├── operator_loc: ∅ + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── equal_loc: (7,6)-(7,7) = "=" + │ └── end_keyword_loc: ∅ + ├── subsequent: ∅ + └── end_keyword_loc: ∅ diff --git a/version.h b/version.h index c6c3d1a3e6b7ec..1c7cd992fcb803 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 15 +#define RUBY_PATCHLEVEL 16 #include "ruby/version.h" #include "ruby/internal/abi.h" From 3db440f5a19ca8d46c2c5d5ec029dbae73cc34f9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:08:20 -0800 Subject: [PATCH 27/48] merge revision(s) f7059af50a31a4d27a04ace0beadb60616f3f971: [Backport #21046] Use no-inline version `rb_current_ec` on Arm64 The TLS across .so issue seems related to Arm64, but not Darwin. --- thread_pthread.h | 4 ++-- version.h | 2 +- vm.c | 2 +- vm_core.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/thread_pthread.h b/thread_pthread.h index dfc8ad42727abc..b632668a2a84ec 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -133,8 +133,8 @@ struct rb_thread_sched { #ifdef RB_THREAD_LOCAL_SPECIFIER NOINLINE(void rb_current_ec_set(struct rb_execution_context_struct *)); - # ifdef __APPLE__ - // on Darwin, TLS can not be accessed across .so + # if defined(__arm64__) || defined(__aarch64__) + // on Arm64, TLS can not be accessed across .so NOINLINE(struct rb_execution_context_struct *rb_current_ec(void)); # else RUBY_EXTERN RB_THREAD_LOCAL_SPECIFIER struct rb_execution_context_struct *ruby_current_ec; diff --git a/version.h b/version.h index 1c7cd992fcb803..f8d79f53d32f3a 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 16 +#define RUBY_PATCHLEVEL 17 #include "ruby/version.h" #include "ruby/internal/abi.h" diff --git a/vm.c b/vm.c index db168e62c07fcb..cff159a663ec60 100644 --- a/vm.c +++ b/vm.c @@ -571,7 +571,7 @@ rb_current_ec_set(rb_execution_context_t *ec) } -#ifdef __APPLE__ +#if defined(__arm64__) || defined(__aarch64__) rb_execution_context_t * rb_current_ec(void) { diff --git a/vm_core.h b/vm_core.h index 8cbf999e3e3cd7..961cc3967c3a6d 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1977,7 +1977,7 @@ static inline rb_execution_context_t * rb_current_execution_context(bool expect_ec) { #ifdef RB_THREAD_LOCAL_SPECIFIER - #ifdef __APPLE__ + #if defined(__arm64__) || defined(__aarch64__) rb_execution_context_t *ec = rb_current_ec(); #else rb_execution_context_t *ec = ruby_current_ec; From 3fdf7279a0b1ac35fdf233464a93b3d268133d5a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:08:44 -0800 Subject: [PATCH 28/48] merge revision(s) e0d600ec190c64aff76cfcbd6009cffb927da166: [Backport #21012] Avoid opt_aset_with optimization inside multiple assignment Previously, since the opt_aset_with optimization was introduced, use of the opt_aset_with optimization inside multiple assignment would result in a segfault or incorrect instructions. Fixes [Bug #21012] Co-authored-by: Nobuyoshi Nakada --- compile.c | 6 +++++- iseq.h | 1 + test/ruby/test_assignment.rb | 10 ++++++++++ version.h | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/compile.c b/compile.c index 3f894cbe695b48..5aebdd94ea37c5 100644 --- a/compile.c +++ b/compile.c @@ -10176,7 +10176,8 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node /* optimization shortcut * obj["literal"] = value -> opt_aset_with(obj, "literal", value) */ - if (mid == idASET && !private_recv_p(node) && RNODE_ATTRASGN(node)->nd_args && + if (!ISEQ_COMPILE_DATA(iseq)->in_masgn && + mid == idASET && !private_recv_p(node) && RNODE_ATTRASGN(node)->nd_args && nd_type_p(RNODE_ATTRASGN(node)->nd_args, NODE_LIST) && RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->as.nd_alen == 2 && (nd_type_p(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head, NODE_STR) || nd_type_p(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head, NODE_FILE)) && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && @@ -10729,7 +10730,10 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_MASGN:{ + bool prev_in_masgn = ISEQ_COMPILE_DATA(iseq)->in_masgn; + ISEQ_COMPILE_DATA(iseq)->in_masgn = true; compile_massign(iseq, ret, node, popped); + ISEQ_COMPILE_DATA(iseq)->in_masgn = prev_in_masgn; break; } diff --git a/iseq.h b/iseq.h index a0b59c441f6a8c..a9af64572e24e4 100644 --- a/iseq.h +++ b/iseq.h @@ -119,6 +119,7 @@ struct iseq_compile_data { struct iseq_compile_data_storage *storage_current; } insn; bool in_rescue; + bool in_masgn; int loopval_popped; /* used by NODE_BREAK */ int last_line; int label_no; diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb index 3a8dafb7f0af00..3d0e773c825664 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -248,6 +248,16 @@ def test_massign_splat a,b,*c = *[*[1,2]]; assert_equal([1,2,[]], [a,b,c]) end + def test_massign_optimized_literal_bug_21012 + a = [] + def a.[]=(*args) + push args + end + a["a", "b"], = 1 + a["a", 10], = 2 + assert_equal [["a", "b", 1], ["a", 10, 2]], a + end + def test_assign_rescue a = raise rescue 2; assert_equal(2, a) a, b = raise rescue [3,4]; assert_equal([3, 4], [a, b]) diff --git a/version.h b/version.h index f8d79f53d32f3a..6267bdc70fe276 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 17 +#define RUBY_PATCHLEVEL 18 #include "ruby/version.h" #include "ruby/internal/abi.h" From 2108ab2cb68e9d975e4dcf035a11243e1173336f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:09:15 -0800 Subject: [PATCH 29/48] merge revision(s) 04ec07794657cd2444ecb001a522b9df2db1b90a: [Backport #21038] Preserve `errno` in `rb_fiber_scheduler_unblock`. (#12576) [Bug #21038] Co-authored-by: Julian Scheid --- scheduler.c | 10 +++++++++- version.h | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scheduler.c b/scheduler.c index bb406a5cf65022..edb38b666c1ae0 100644 --- a/scheduler.c +++ b/scheduler.c @@ -412,7 +412,15 @@ rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber) { RUBY_ASSERT(rb_obj_is_fiber(fiber)); - return rb_funcall(scheduler, id_unblock, 2, blocker, fiber); + // `rb_fiber_scheduler_unblock` can be called from points where `errno` is expected to be preserved. Therefore, we should save and restore it. For example `io_binwrite` calls `rb_fiber_scheduler_unblock` and if `errno` is reset to 0 by user code, it will break the error handling in `io_write`. + // If we explicitly preserve `errno` in `io_binwrite` and other similar functions (e.g. by returning it), this code is no longer needed. I hope in the future we will be able to remove it. + int saved_errno = errno; + + VALUE result = rb_funcall(scheduler, id_unblock, 2, blocker, fiber); + + errno = saved_errno; + + return result; } /* diff --git a/version.h b/version.h index 6267bdc70fe276..ce08b11d47ab63 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 18 +#define RUBY_PATCHLEVEL 19 #include "ruby/version.h" #include "ruby/internal/abi.h" From ead3bbc2405ad1df2228c44133ee1c6574ef5973 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:09:41 -0800 Subject: [PATCH 30/48] merge revision(s) d4a1a2780c39bc648496ac92fc6e6ce2eb38ab47: [Backport #21032] rb_feature_p: skip `get_expanded_load_path` for absolute paths Ref: https://github.com/fxn/zeitwerk/pull/308 ```ruby require 'benchmark' $LOAD_PATH << 'relative-path' autoload :FOO, '/tmp/foo.rb' puts Benchmark.realtime { 500_000.times do Object.autoload?(:FOO) end } ``` The above script takes 2.5 seconds on `master`, and only 50ms on this branch. When we're looking for a feature with an absolute path, we don't need to call the expensive `get_expanded_load_path`. --- load.c | 2 +- version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/load.c b/load.c index 68ee9d86ef293c..4ccd2ed20f40d6 100644 --- a/load.c +++ b/load.c @@ -597,7 +597,7 @@ rb_feature_p(rb_vm_t *vm, const char *feature, const char *ext, int rb, int expa loading_tbl = get_loading_table(vm); f = 0; - if (!expanded) { + if (!expanded && !rb_is_absolute_path(feature)) { struct loaded_feature_searching fs; fs.name = feature; fs.len = len; diff --git a/version.h b/version.h index ce08b11d47ab63..f97a24982cb710 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 19 +#define RUBY_PATCHLEVEL 20 #include "ruby/version.h" #include "ruby/internal/abi.h" From 2ee25f7b47a30b7515cdb4da341d28931d8b5810 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:11:06 -0800 Subject: [PATCH 31/48] merge revision(s) 2b6fc9ea7212543a1be26768403f59c7a759b5ea: [Backport #21092] [Bug #21092] Fallback variables after execonf has done When reading from a dummy makefile, the global variables initialized in `init_mkmf` may not be overridden. --- ext/extmk.rb | 6 +++--- version.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/extmk.rb b/ext/extmk.rb index 8b6b365a99d8ab..1d165452f13a8d 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -173,8 +173,6 @@ def extmake(target, basedir = 'ext', maybestatic = true) $mdir = target $srcdir = File.join($top_srcdir, basedir, $mdir) $preload = nil - $objs = [] - $srcs = [] $extso = [] makefile = "./Makefile" static = $static @@ -208,7 +206,7 @@ def extmake(target, basedir = 'ext', maybestatic = true) begin $extconf_h = nil ok &&= extract_makefile(makefile) - old_objs = $objs + old_objs = $objs || [] old_cleanfiles = $distcleanfiles | $cleanfiles conf = ["#{$srcdir}/makefile.rb", "#{$srcdir}/extconf.rb"].find {|f| File.exist?(f)} if (!ok || ($extconf_h && !File.exist?($extconf_h)) || @@ -271,6 +269,8 @@ def extmake(target, basedir = 'ext', maybestatic = true) unless $destdir.to_s.empty? or $mflags.defined?("DESTDIR") args += ["DESTDIR=" + relative_from($destdir, "../"+prefix)] end + $objs ||= [] + $srcs ||= [] if $static and ok and !$objs.empty? and !noinstall args += ["static"] $extlist.push [(maybestatic ? $static : false), target, $target, $preload] diff --git a/version.h b/version.h index f97a24982cb710..6e314d198b8cc9 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 20 +#define RUBY_PATCHLEVEL 21 #include "ruby/version.h" #include "ruby/internal/abi.h" From feb4a688a24a1966c8d08962a1104001234efcae Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:12:07 -0800 Subject: [PATCH 32/48] merge revision(s) 46b544c54955348ef1ea9692b837b061f59f91cd, d3abee739f4feb91bb9aaae33877d70c8c576db0: [Backport #21095] Prefer `uname -n` over `hostname`. (#12647) Add fallback for `hostname` if `uname` isn't available. (#12655) --- spec/ruby/library/socket/socket/gethostname_spec.rb | 10 +++++++++- version.h | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/ruby/library/socket/socket/gethostname_spec.rb b/spec/ruby/library/socket/socket/gethostname_spec.rb index 4b79747b2721e5..89e1ed496f10b0 100644 --- a/spec/ruby/library/socket/socket/gethostname_spec.rb +++ b/spec/ruby/library/socket/socket/gethostname_spec.rb @@ -2,7 +2,15 @@ require_relative '../fixtures/classes' describe "Socket.gethostname" do + def system_hostname + # Most platforms implement this POSIX standard: + `uname -n`.strip + rescue + # Only really required for Windows without MSYS/MinGW/Cygwin etc: + `hostname`.strip + end + it "returns the host name" do - Socket.gethostname.should == `hostname`.strip + Socket.gethostname.should == system_hostname end end diff --git a/version.h b/version.h index 6e314d198b8cc9..1a3a464e582c06 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 21 +#define RUBY_PATCHLEVEL 22 #include "ruby/version.h" #include "ruby/internal/abi.h" From e5403bd137d57991d8788cd29bbb5916d75acb36 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:12:33 -0800 Subject: [PATCH 33/48] merge revision(s) db02a6b3ab4cabbdf492c26dcb1929b4ef0370a1: [Backport #21103] [Bug #21103] Fix local variable index calculation with forwarding Forwarding argument is optimized not to packed when no other arguments and an internal object refers values before it. This size is decided at called time, calculate the local variable index from the fixed end point. --- proc.c | 8 ++-- test/ruby/test_method.rb | 100 +++++++++++++++++++++++++++++++++++++++ version.h | 2 +- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/proc.c b/proc.c index c0fcb20b956136..7a3302cd26854f 100644 --- a/proc.c +++ b/proc.c @@ -414,11 +414,11 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) } const rb_iseq_t *iseq = env->iseq; - unsigned int i; VM_ASSERT(rb_obj_is_iseq((VALUE)iseq)); - for (i=0; ilocal_table_size; i++) { + const unsigned int local_table_size = ISEQ_BODY(iseq)->local_table_size; + for (unsigned int i=0; ilocal_table[i] == lid) { if (ISEQ_BODY(iseq)->local_iseq == iseq && ISEQ_BODY(iseq)->param.flags.has_block && @@ -431,7 +431,9 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) } *envp = env; - return &env->env[i]; + unsigned int last_lvar = env->env_size+VM_ENV_INDEX_LAST_LVAR + - 1 /* errinfo */; + return &env->env[last_lvar - (local_table_size - i)]; } } } diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index 5d5d5aac02fbde..a865f6100bb94e 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1439,6 +1439,46 @@ class C def foo a = b = c = a = b = c = 12345 end + + def binding_noarg + a = a = 12345 + binding + end + + def binding_one_arg(x) + a = a = 12345 + binding + end + + def binding_optargs(x, y=42) + a = a = 12345 + binding + end + + def binding_anyargs(*x) + a = a = 12345 + binding + end + + def binding_keywords(x: 42) + a = a = 12345 + binding + end + + def binding_anykeywords(**x) + a = a = 12345 + binding + end + + def binding_forwarding(...) + a = a = 12345 + binding + end + + def binding_forwarding1(x, ...) + a = a = 12345 + binding + end end def test_to_proc_binding @@ -1457,6 +1497,66 @@ def test_to_proc_binding assert_equal([:bar, :foo], b.local_variables.sort, bug11012) end + def test_method_binding + c = C.new + + b = c.binding_noarg + assert_equal(12345, b.local_variable_get(:a)) + + b = c.binding_one_arg(0) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(0, b.local_variable_get(:x)) + + b = c.binding_anyargs() + assert_equal(12345, b.local_variable_get(:a)) + assert_equal([], b.local_variable_get(:x)) + b = c.binding_anyargs(0) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal([0], b.local_variable_get(:x)) + b = c.binding_anyargs(0, 1) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal([0, 1], b.local_variable_get(:x)) + + b = c.binding_optargs(0) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(0, b.local_variable_get(:x)) + assert_equal(42, b.local_variable_get(:y)) + b = c.binding_optargs(0, 1) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(0, b.local_variable_get(:x)) + assert_equal(1, b.local_variable_get(:y)) + + b = c.binding_keywords() + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(42, b.local_variable_get(:x)) + b = c.binding_keywords(x: 102) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(102, b.local_variable_get(:x)) + + b = c.binding_anykeywords() + assert_equal(12345, b.local_variable_get(:a)) + assert_equal({}, b.local_variable_get(:x)) + b = c.binding_anykeywords(foo: 999) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal({foo: 999}, b.local_variable_get(:x)) + + b = c.binding_forwarding() + assert_equal(12345, b.local_variable_get(:a)) + b = c.binding_forwarding(0) + assert_equal(12345, b.local_variable_get(:a)) + b = c.binding_forwarding(0, 1) + assert_equal(12345, b.local_variable_get(:a)) + b = c.binding_forwarding(foo: 42) + assert_equal(12345, b.local_variable_get(:a)) + + b = c.binding_forwarding1(987) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(987, b.local_variable_get(:x)) + b = c.binding_forwarding1(987, 654) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(987, b.local_variable_get(:x)) + end + MethodInMethodClass_Setup = -> do remove_const :MethodInMethodClass if defined? MethodInMethodClass diff --git a/version.h b/version.h index 1a3a464e582c06..2d56275a352d92 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 22 +#define RUBY_PATCHLEVEL 23 #include "ruby/version.h" #include "ruby/internal/abi.h" From 319c3c70385a63aaea3c1a68a79c70cfd533c6b6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:13:47 -0800 Subject: [PATCH 34/48] merge revision(s) 1683dadb19876f0a64589bdbbcf6fff8143f78ff: [Backport #21088] Do not save ResolutionError if resolution succeeds for any address family (#12678) * Do not save ResolutionError if resolution succeeds for any address family Socket with Happy Eyeballs Version 2 performs connection attempts and name resolution in parallel. In the existing implementation, if a connection attempt failed for one address family while name resolution was still in progress for the other, and that name resolution later failed, the method would terminate with a name resolution error. This behavior was intended to ensure that the final error reflected the most recent failure, potentially overriding an earlier error. However, [Bug #21088](https://bugs.ruby-lang.org/issues/21088) made me realize that terminating with a name resolution error is unnatural when name resolution succeeded for at least one address family. This PR modifies the behavior so that if name resolution succeeds for one address family, any name resolution error from the other is not saved. This PR includes the following changes: * Do not display select(2) as the system call that caused the raised error, as it is for internal processing * Fix bug: Get errno with Socket::SO_ERROR in Windows environment with a workaround for tests not passing --- ext/socket/ipsocket.c | 33 ++++++++++++++++++++------------- ext/socket/lib/socket.rb | 9 ++++++--- test/socket/test_socket.rb | 22 ++++++++++++++++++++++ test/socket/test_tcp.rb | 2 +- version.h | 2 +- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 3e497a43be8d4a..ee1bc4a77b36ae 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -892,7 +892,6 @@ init_fast_fallback_inetsock_internal(VALUE v) } status = rb_thread_fd_select(nfds, &arg->readfds, &arg->writefds, NULL, delay_p); - syscall = "select(2)"; now = current_clocktime_ts(); if (is_timeout_tv(resolution_delay_expires_at, now)) { @@ -998,9 +997,11 @@ init_fast_fallback_inetsock_internal(VALUE v) if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err && arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err != EAI_ADDRFAMILY) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v4.finished || resolution_store.v4.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v6.has_error = true; } else { resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai; @@ -1015,9 +1016,11 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v4.finished = true; if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v6.finished || resolution_store.v6.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v4.has_error = true; } else { resolution_store.v4.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai; @@ -1057,9 +1060,11 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v6.finished = true; if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v4.finished || resolution_store.v4.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v6.has_error = true; } else { resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai; @@ -1075,9 +1080,11 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v4.finished = true; if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v6.finished || resolution_store.v6.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v4.has_error = true; } else { resolution_store.v4.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai; diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 6a1ddf9eda7744..5852934ef0570b 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -833,7 +833,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, if except_sockets&.any? except_sockets.each do |except_socket| failed_ai = connecting_sockets.delete except_socket - sockopt = except_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_CONNECT_TIME) + sockopt = except_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) except_socket.close ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address last_error = SystemCallError.new("connect(2) for #{ip_address}:#{failed_ai.ip_port}", sockopt.int) @@ -862,7 +862,10 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, unless (Socket.const_defined?(:EAI_ADDRFAMILY)) && (result.is_a?(Socket::ResolutionError)) && (result.error_code == Socket::EAI_ADDRFAMILY) - last_error = result + other = family_name == :ipv6 ? :ipv4 : :ipv6 + if !resolution_store.resolved?(other) || !resolution_store.resolved_successfully?(other) + last_error = result + end end else resolution_store.add_resolved(family_name, result) @@ -1068,7 +1071,7 @@ def resolved?(family) end def resolved_successfully?(family) - resolved?(family) && !!@error_dict[family] + resolved?(family) && !@error_dict[family] end def resolved_all_families? diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 4d75caab509d23..27e60b333575d6 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -995,6 +995,28 @@ def test_tcp_socket_all_hostname_resolution_failed RUBY end + def test_tcp_socket_hostname_resolution_failed_after_connection_failure + opts = %w[-rsocket -W1] + assert_separately opts, <<~RUBY + server = TCPServer.new("127.0.0.1", 0) + port = server.connect_address.ip_port + + Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| + case family + when Socket::AF_INET6 then sleep(0.1); raise Socket::ResolutionError + when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)] + end + end + + server.close + + # SystemCallError is a workaround for Windows environment + assert_raise(Errno::ECONNREFUSED, SystemCallError) do + Socket.tcp("localhost", port) + end + RUBY + end + def test_tcp_socket_v6_address_passed opts = %w[-rsocket -W1] assert_separately opts, <<~RUBY diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index 4984a7e7bcdcd3..e6a41f56604665 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -316,7 +316,7 @@ def test_initialize_with_hostname_resolution_failure_after_connection_failure port = server.connect_address.ip_port server.close - assert_raise(Socket::ResolutionError) do + assert_raise(Errno::ECONNREFUSED) do TCPSocket.new( "localhost", port, diff --git a/version.h b/version.h index 2d56275a352d92..07c23c93a8e5a0 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 23 +#define RUBY_PATCHLEVEL 24 #include "ruby/version.h" #include "ruby/internal/abi.h" From e3b16320e6b39e81606771f507278bf14045fa13 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:14:14 -0800 Subject: [PATCH 35/48] merge revision(s) 91a10c07579f282a94e4b5830feaeca87f9a7dd3: [Backport #21112] Fix a typo in WeakKeyMap argument error [Bug #21112] --- version.h | 2 +- weakmap.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 07c23c93a8e5a0..7dfea6f354e1ed 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 24 +#define RUBY_PATCHLEVEL 25 #include "ruby/version.h" #include "ruby/internal/abi.h" diff --git a/weakmap.c b/weakmap.c index f8a6ae3747bd5a..2ebf7d204f7195 100644 --- a/weakmap.c +++ b/weakmap.c @@ -859,7 +859,7 @@ wkmap_aset(VALUE self, VALUE key, VALUE val) TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) { - rb_raise(rb_eArgError, "WeakKeyMap must be garbage collectable"); + rb_raise(rb_eArgError, "WeakKeyMap keys must be garbage collectable"); UNREACHABLE_RETURN(Qnil); } From d3fc56dcfa7b408cc3b6788efad36fd8df3e55da Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:17:04 -0800 Subject: [PATCH 36/48] merge revision(s) 127325a4bad409ee5da91084fac768934a8fd9e3: [Backport #21117] [ruby/prism] No writing to numbered parameters Fixes [Bug #21117] https://github.com/ruby/prism/commit/19d4bab5a0 --- lib/prism/parse_result/newlines.rb | 2 +- prism/prism.c | 15 +++++++++++++++ test/prism/errors/numbered_and_write.txt | 3 +++ test/prism/errors/numbered_operator_write.txt | 3 +++ test/prism/errors/numbered_or_write.txt | 3 +++ test/prism/errors_test.rb | 5 ++++- version.h | 2 +- 7 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 test/prism/errors/numbered_and_write.txt create mode 100644 test/prism/errors/numbered_operator_write.txt create mode 100644 test/prism/errors/numbered_or_write.txt diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index a04fa78a75c8b8..37f64f8ae2ccd0 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -63,7 +63,7 @@ def visit_statements_node(node) class Node def newline_flag? # :nodoc: - @newline_flag ? true : false + !!defined?(@newline_flag) end def newline_flag!(lines) # :nodoc: diff --git a/prism/prism.c b/prism/prism.c index 8e30f096297f2c..37910f981f3a1c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -21058,6 +21058,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t return result; } case PM_LOCAL_VARIABLE_READ_NODE: { + if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { + PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); + parse_target_implicit_parameter(parser, node); + } + pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; parser_lex(parser); @@ -21176,6 +21181,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t return result; } case PM_LOCAL_VARIABLE_READ_NODE: { + if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { + PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); + parse_target_implicit_parameter(parser, node); + } + pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; parser_lex(parser); @@ -21304,6 +21314,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t return result; } case PM_LOCAL_VARIABLE_READ_NODE: { + if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { + PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); + parse_target_implicit_parameter(parser, node); + } + pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; parser_lex(parser); diff --git a/test/prism/errors/numbered_and_write.txt b/test/prism/errors/numbered_and_write.txt new file mode 100644 index 00000000000000..f80b97b2d58d06 --- /dev/null +++ b/test/prism/errors/numbered_and_write.txt @@ -0,0 +1,3 @@ +tap { _1 &&= 1 } + ^~ _1 is reserved for numbered parameters + diff --git a/test/prism/errors/numbered_operator_write.txt b/test/prism/errors/numbered_operator_write.txt new file mode 100644 index 00000000000000..70cd58c81132ca --- /dev/null +++ b/test/prism/errors/numbered_operator_write.txt @@ -0,0 +1,3 @@ +tap { _1 += 1 } + ^~ _1 is reserved for numbered parameters + diff --git a/test/prism/errors/numbered_or_write.txt b/test/prism/errors/numbered_or_write.txt new file mode 100644 index 00000000000000..b27495498d05a5 --- /dev/null +++ b/test/prism/errors/numbered_or_write.txt @@ -0,0 +1,3 @@ +tap { _1 ||= 1 } + ^~ _1 is reserved for numbered parameters + diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index cb2fd48d371470..62bbd8458b2ca9 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -14,7 +14,10 @@ class ErrorsTest < TestCase "targeting_numbered_parameter.txt", "defining_numbered_parameter.txt", "defining_numbered_parameter_2.txt", - "numbered_parameters_in_block_arguments.txt" + "numbered_parameters_in_block_arguments.txt", + "numbered_and_write.txt", + "numbered_or_write.txt", + "numbered_operator_write.txt" ] end diff --git a/version.h b/version.h index 7dfea6f354e1ed..782a4fce30ce7e 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 25 +#define RUBY_PATCHLEVEL 26 #include "ruby/version.h" #include "ruby/internal/abi.h" From 9a0984436888a59f4914c3035c53657baf62ce2b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:17:41 -0800 Subject: [PATCH 37/48] merge revision(s) b21e1aed2ed5b22b50efc658289a403eeed581df: [Backport #21114] [ruby/prism] Fix infinite loop in error recovery When recovering from a depth error that occurs at the end of the file, we need to break out of parsing statements. Fixes [Bug #21114] https://github.com/ruby/prism/commit/a32e268787 --- prism/prism.c | 9 +++++++++ .../block_beginning_with_brace_and_ending_with_end.txt | 1 - version.h | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 37910f981f3a1c..36b3640559e35d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13937,6 +13937,15 @@ parse_statements(pm_parser_t *parser, pm_context_t context, uint16_t depth) { if (PM_NODE_TYPE_P(node, PM_MISSING_NODE)) { parser_lex(parser); + // If we are at the end of the file, then we need to stop parsing + // the statements entirely at this point. Mark the parser as + // recovering, as we know that EOF closes the top-level context, and + // then break out of the loop. + if (match1(parser, PM_TOKEN_EOF)) { + parser->recovering = true; + break; + } + while (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)); if (context_terminator(context, &parser->current)) break; } else if (!accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_EOF)) { diff --git a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt index f0fa964c8a7858..16af8200ec8930 100644 --- a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt +++ b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt @@ -1,6 +1,5 @@ x.each { x end ^~~ unexpected 'end', expecting end-of-input ^~~ unexpected 'end', ignoring it - ^ unexpected end-of-input, assuming it is closing the parent top level context ^ expected a block beginning with `{` to end with `}` diff --git a/version.h b/version.h index 782a4fce30ce7e..0f0b6f7462014b 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 26 +#define RUBY_PATCHLEVEL 27 #include "ruby/version.h" #include "ruby/internal/abi.h" From 18daf8326a370d674b9e8aa6d124eafe1b53b847 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Jan 2025 18:02:16 +0900 Subject: [PATCH 38/48] Use configu.guess with wasi compatible version --- .github/workflows/wasm.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 3924aaeb9905ca..7c2a384bae999c 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -114,6 +114,12 @@ jobs: 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 \ From d5666bbf9191bb990096faa159ee2fb8d7c86f92 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 12 Feb 2025 11:43:05 +0000 Subject: [PATCH 39/48] tool/downloader.rb: Stop caching already existing files Previously, the script was caching any file already present in the destination directory, regardless of its origin. This caused issues when the directory contained files copied from external sources like `autoreconf --install`. For example: 1. `./autogen.sh --install` copies `config.guess` and `config.sub` from the system to `./tool`. 2. `ruby tool/downloader.rb -d tool -e gnu config.guess config.sub` treats those files as if they were downloaded and caches them. 3. Removing the files: `rm tool/config.guess tool/config.sub`. 4. Running the downloader again, it mistakenly restores the cached files instead of downloading fresh versions. --- tool/downloader.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/tool/downloader.rb b/tool/downloader.rb index 3a91ea0b938aed..89adfabd1ab7e5 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -218,9 +218,6 @@ def self.download(url, name, dir = nil, since = true, options = {}) $stdout.puts "#{file} already exists" $stdout.flush end - if cache_save - save_cache(cache, file, name) - end return file.to_path end if dryrun From de7a196bd4b22f6073e6cf6c2d7d6fd8171b6baa Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:20:21 -0800 Subject: [PATCH 40/48] Revert "merge revision(s) cb419e3912f0514b8151469b0a4a4b83cbbcce78: [Backport #21031]" This reverts commit 7adf89d7ad30552d7e57709d24eec266f601d38b. --- prism_compile.c | 6 ++++ prism_compile.h | 6 ---- test/ruby/test_syntax.rb | 10 ------ vm_eval.c | 72 ++++------------------------------------ 4 files changed, 13 insertions(+), 81 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 362aecfbb046a4..a63bf490f59611 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -138,6 +138,12 @@ pm_iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int node #define PM_COMPILE_NOT_POPPED(node) \ pm_compile_node(iseq, (node), ret, false, scope_node) +#define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) +#define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) + #define PM_NODE_START_LOCATION(parser, node) \ ((pm_node_location_t) { .line = pm_newline_list_line(&(parser)->newline_list, ((const pm_node_t *) (node))->location.start, (parser)->start_line), .node_id = ((const pm_node_t *) (node))->node_id }) diff --git a/prism_compile.h b/prism_compile.h index c032449bd65ca9..f18fdbf892a9a8 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -83,12 +83,6 @@ typedef struct { bool parsed; } pm_parse_result_t; -#define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) -#define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) - VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath, bool load_error); VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 62f1d99bdc855f..a226b10d947cac 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -139,11 +139,6 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** inner(&) end assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10}) - - def evaled(&) - eval("inner(&)") - end - assert_equal(1, evaled{1}) end; end @@ -161,10 +156,8 @@ def test_anonymous_rest_forwarding def b(*); c(*) end def c(*a); a end def d(*); b(*, *) end - def e(*); eval("b(*)") end assert_equal([1, 2], b(1, 2)) assert_equal([1, 2, 1, 2], d(1, 2)) - assert_equal([1, 2], e(1, 2)) end; end @@ -184,12 +177,10 @@ def c(**kw); kw end def d(**); b(k: 1, **) end def e(**); b(**, k: 1) end def f(a: nil, **); b(**) end - def g(**); eval("b(**)") end assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) assert_equal({k: 3}, f(a: 1, k: 3)) - assert_equal({a: 1, k: 3}, g(a: 1, k: 3)) end; end @@ -2019,7 +2010,6 @@ def obj1.bar(*args, **kws, &block) obj4 = obj1.clone obj5 = 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__) diff --git a/vm_eval.c b/vm_eval.c index 74dfd40907dbfa..a8c12707ec3e65 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1692,24 +1692,12 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, iseq = parent; rb_encoding *encoding = rb_enc_get(src); -#define FORWARDING_POSITIONALS_CHR '*' -#define FORWARDING_POSITIONALS_STR "*" -#define FORWARDING_KEYWORDS_CHR ':' -#define FORWARDING_KEYWORDS_STR ":" -#define FORWARDING_BLOCK_CHR '&' -#define FORWARDING_BLOCK_STR "&" -#define FORWARDING_ALL_CHR '.' -#define FORWARDING_ALL_STR "." - for (int scopes_index = 0; scopes_index < scopes_count; scopes_index++) { VALUE iseq_value = (VALUE)iseq; int locals_count = ISEQ_BODY(iseq)->local_table_size; - pm_options_scope_t *options_scope = &result.options.scopes[scopes_count - scopes_index - 1]; pm_options_scope_init(options_scope, locals_count); - uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; - for (int local_index = 0; local_index < locals_count; local_index++) { pm_string_t *scope_local = &options_scope->locals[local_index]; ID local = ISEQ_BODY(iseq)->local_table[local_index]; @@ -1741,23 +1729,10 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, RB_GC_GUARD(name_obj); - pm_string_owned_init(scope_local, (uint8_t *) name_dup, length); - } else if (local == idMULT) { - forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS; - pm_string_constant_init(scope_local, FORWARDING_POSITIONALS_STR, 1); - } else if (local == idPow) { - forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS; - pm_string_constant_init(scope_local, FORWARDING_KEYWORDS_STR, 1); - } else if (local == idAnd) { - forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK; - pm_string_constant_init(scope_local, FORWARDING_BLOCK_STR, 1); - } else if (local == idDot3) { - forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL; - pm_string_constant_init(scope_local, FORWARDING_ALL_STR, 1); + pm_string_owned_init(scope_local, (uint8_t *)name_dup, length); } } - pm_options_scope_forwarding_set(options_scope, forwarding); iseq = ISEQ_BODY(iseq)->parent_iseq; /* We need to GC guard the iseq because the code above malloc memory @@ -1800,38 +1775,14 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, for (int local_index = 0; local_index < locals_count; local_index++) { const pm_string_t *scope_local = &options_scope->locals[local_index]; - pm_constant_id_t constant_id = 0; - - const uint8_t *source = pm_string_source(scope_local); - size_t length = pm_string_length(scope_local); - - if (length > 0) { - if (length == 1) { - switch (*source) { - case FORWARDING_POSITIONALS_CHR: - constant_id = PM_CONSTANT_MULT; - break; - case FORWARDING_KEYWORDS_CHR: - constant_id = PM_CONSTANT_POW; - break; - case FORWARDING_BLOCK_CHR: - constant_id = PM_CONSTANT_AND; - break; - case FORWARDING_ALL_CHR: - constant_id = PM_CONSTANT_DOT3; - break; - default: - constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); - break; - } - } - else { - constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); - } - st_insert(parent_scope->index_lookup_table, (st_data_t) constant_id, (st_data_t) local_index); + pm_constant_id_t constant_id = 0; + if (pm_string_length(scope_local) > 0) { + constant_id = pm_constant_pool_insert_constant( + &result.parser.constant_pool, pm_string_source(scope_local), + pm_string_length(scope_local)); + st_insert(parent_scope->index_lookup_table, (st_data_t)constant_id, (st_data_t)local_index); } - pm_constant_id_list_append(&parent_scope->locals, constant_id); } @@ -1840,15 +1791,6 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, iseq = ISEQ_BODY(iseq)->parent_iseq; } -#undef FORWARDING_POSITIONALS_CHR -#undef FORWARDING_POSITIONALS_STR -#undef FORWARDING_KEYWORDS_CHR -#undef FORWARDING_KEYWORDS_STR -#undef FORWARDING_BLOCK_CHR -#undef FORWARDING_BLOCK_STR -#undef FORWARDING_ALL_CHR -#undef FORWARDING_ALL_STR - int error_state; iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, 0, &error_state); From 270129fdcb923654d9741465ec26e179507e8c96 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 13 Feb 2025 21:29:23 -0800 Subject: [PATCH 41/48] merge revision(s) 19c39e4cfaa467e69b9848c9c5496d7f50d39c7f, d78ff6a767ca813ac5fa178dd7611f20a993c191: [Backport #20984] [Bug #20984] ENV.inspect should be encoding aware [Bug #20984] Fix test with locale encoding --- hash.c | 13 ++++++------- test/ruby/test_env.rb | 16 ++++++++++++---- version.h | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/hash.c b/hash.c index ddbc01e5e2572f..6526ec88705853 100644 --- a/hash.c +++ b/hash.c @@ -5909,24 +5909,23 @@ env_to_s(VALUE _) static VALUE env_inspect(VALUE _) { - VALUE i; VALUE str = rb_str_buf_new2("{"); + rb_encoding *enc = env_encoding(); ENV_LOCK(); { char **env = GET_ENVIRON(environ); while (*env) { - char *s = strchr(*env, '='); + const char *s = strchr(*env, '='); if (env != environ) { rb_str_buf_cat2(str, ", "); } if (s) { - rb_str_buf_cat2(str, "\""); - rb_str_buf_cat(str, *env, s-*env); - rb_str_buf_cat2(str, "\"=>"); - i = rb_inspect(rb_str_new2(s+1)); - rb_str_buf_append(str, i); + rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(*env, s-*env, enc))); + rb_str_buf_cat2(str, "=>"); + s++; + rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(s, strlen(s), enc))); } env++; } diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index 74f4750b134357..1e448780656c75 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -2,7 +2,9 @@ require 'test/unit' class TestEnv < Test::Unit::TestCase - IGNORE_CASE = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM + windows = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM + IGNORE_CASE = windows + ENCODING = windows ? Encoding::UTF_8 : Encoding.find("locale") PATH_ENV = "PATH" INVALID_ENVVARS = [ "foo\0bar", @@ -353,6 +355,13 @@ def test_inspect end end + def test_inspect_encoding + ENV.clear + key = "VAR\u{e5 e1 e2 e4 e3 101 3042}" + ENV[key] = "foo" + assert_equal(%{{"VAR\u{e5 e1 e2 e4 e3 101 3042}"=>"foo"}}, ENV.inspect) + end + def test_to_a ENV.clear ENV["foo"] = "bar" @@ -403,8 +412,7 @@ def test_assoc assert_equal("foo", v) end assert_invalid_env {|var| ENV.assoc(var)} - encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale") - assert_equal(encoding, v.encoding) + assert_equal(ENCODING, v.encoding) end def test_has_value2 @@ -517,7 +525,7 @@ def test_huge_value assert_equal(huge_value, ENV["foo"]) end - if /mswin|mingw/ =~ RUBY_PLATFORM + if windows def windows_version @windows_version ||= %x[ver][/Version (\d+)/, 1].to_i end diff --git a/version.h b/version.h index 0f0b6f7462014b..8e60c598aadb99 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 27 +#define RUBY_PATCHLEVEL 28 #include "ruby/version.h" #include "ruby/internal/abi.h" From 80f2329f76b76ce7bd4fcd9a878b2bc5b0626b26 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 26 Dec 2024 13:02:29 +0900 Subject: [PATCH 42/48] Refine ENV tests --- test/ruby/test_env.rb | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index 1e448780656c75..53d1a052264a6c 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -347,12 +347,16 @@ def test_inspect ENV["foo"] = "bar" ENV["baz"] = "qux" s = ENV.inspect + expected = [%("foo"=>"bar"), %("baz"=>"qux")] + unless s.start_with?(/\{"foo"/i) + expected.reverse! + end + expected = '{' + expected.join(', ') + '}' if IGNORE_CASE s = s.upcase - assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}') - else - assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}') + expected = expected.upcase end + assert_equal(expected, s) end def test_inspect_encoding @@ -368,12 +372,7 @@ def test_to_a ENV["baz"] = "qux" a = ENV.to_a assert_equal(2, a.size) - if IGNORE_CASE - a = a.map {|x| x.map {|y| y.upcase } } - assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)]) - else - assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)]) - end + check([%w(baz qux), %w(foo bar)], a) end def test_rehash @@ -458,13 +457,14 @@ def test_reject assert_equal(h1, h2) end - def check(as, bs) + def assert_equal_env(as, bs) if IGNORE_CASE as = as.map {|k, v| [k.upcase, v] } bs = bs.map {|k, v| [k.upcase, v] } end assert_equal(as.sort, bs.sort) end + alias check assert_equal_env def test_shift ENV.clear @@ -1097,12 +1097,16 @@ def test_inspect_in_ractor Ractor.yield s end s = r.take + expected = ['"foo"=>"bar"', '"baz"=>"qux"'] + unless s.start_with?(/\{"foo"/i) + expected.reverse! + end + expected = "{" + expected.join(', ') + "}" if #{ignore_case_str} s = s.upcase - assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}') - else - assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}') + expected = expected.upcase end + assert_equal(expected, s) end; end @@ -1117,12 +1121,13 @@ def test_to_a_in_ractor end a = r.take assert_equal(2, a.size) + expected = [%w(baz qux), %w(foo bar)] if #{ignore_case_str} - a = a.map {|x| x.map {|y| y.upcase } } - assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)]) - else - assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)]) + a = a.map {|x, y| [x.upcase, y]} + expected.map! {|x, y| [x.upcase, y]} end + a.sort! + assert_equal(expected, a) end; end From 24f48c83e7e851a6ca712fc7b498634da9a24789 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 26 Dec 2024 15:01:48 +0900 Subject: [PATCH 43/48] [Bug #20982] Put spaces in `ENV.inspect` results as well as `Hash` --- hash.c | 2 +- spec/ruby/core/env/inspect_spec.rb | 2 +- test/ruby/test_env.rb | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hash.c b/hash.c index 6526ec88705853..17bd1447b7c1db 100644 --- a/hash.c +++ b/hash.c @@ -5923,7 +5923,7 @@ env_inspect(VALUE _) } if (s) { rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(*env, s-*env, enc))); - rb_str_buf_cat2(str, "=>"); + rb_str_buf_cat2(str, " => "); s++; rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(s, strlen(s), enc))); } diff --git a/spec/ruby/core/env/inspect_spec.rb b/spec/ruby/core/env/inspect_spec.rb index 3c611c24a177c8..7dd92b120ffbb2 100644 --- a/spec/ruby/core/env/inspect_spec.rb +++ b/spec/ruby/core/env/inspect_spec.rb @@ -4,7 +4,7 @@ it "returns a String that looks like a Hash with real data" do ENV["foo"] = "bar" - ENV.inspect.should =~ /\{.*"foo"=>"bar".*\}/ + ENV.inspect.should =~ /\{.*"foo" *=> *"bar".*\}/ ENV.delete "foo" end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index 53d1a052264a6c..a68d9c00a2871c 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -347,7 +347,7 @@ def test_inspect ENV["foo"] = "bar" ENV["baz"] = "qux" s = ENV.inspect - expected = [%("foo"=>"bar"), %("baz"=>"qux")] + expected = [%("foo" => "bar"), %("baz" => "qux")] unless s.start_with?(/\{"foo"/i) expected.reverse! end @@ -363,7 +363,7 @@ def test_inspect_encoding ENV.clear key = "VAR\u{e5 e1 e2 e4 e3 101 3042}" ENV[key] = "foo" - assert_equal(%{{"VAR\u{e5 e1 e2 e4 e3 101 3042}"=>"foo"}}, ENV.inspect) + assert_equal(%{{"VAR\u{e5 e1 e2 e4 e3 101 3042}" => "foo"}}, ENV.inspect) end def test_to_a @@ -1097,7 +1097,7 @@ def test_inspect_in_ractor Ractor.yield s end s = r.take - expected = ['"foo"=>"bar"', '"baz"=>"qux"'] + expected = ['"foo" => "bar"', '"baz" => "qux"'] unless s.start_with?(/\{"foo"/i) expected.reverse! end From 45fe3c137b6cc0b2546493e37d6334d8f39e076d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 14 Feb 2025 12:51:14 -0500 Subject: [PATCH 44/48] Backport Bug #21031 --- prism/options.c | 12 +++++++ prism/options.h | 27 +++++++++++++++ prism/prism.c | 2 +- prism_compile.c | 6 ---- prism_compile.h | 6 ++++ test/ruby/test_syntax.rb | 10 ++++++ vm_eval.c | 72 ++++++++++++++++++++++++++++++++++++---- 7 files changed, 121 insertions(+), 14 deletions(-) diff --git a/prism/options.c b/prism/options.c index 6b52b2f296034c..4018d4a13f2360 100644 --- a/prism/options.c +++ b/prism/options.c @@ -163,6 +163,7 @@ PRISM_EXPORTED_FUNCTION bool pm_options_scope_init(pm_options_scope_t *scope, size_t locals_count) { scope->locals_count = locals_count; scope->locals = xcalloc(locals_count, sizeof(pm_string_t)); + scope->forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; return scope->locals != NULL; } @@ -174,6 +175,14 @@ pm_options_scope_local_get(const pm_options_scope_t *scope, size_t index) { return &scope->locals[index]; } +/** + * Set the forwarding option on the given scope struct. + */ +PRISM_EXPORTED_FUNCTION void +pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding) { + scope->forwarding = forwarding; +} + /** * Free the internal memory associated with the options. */ @@ -281,6 +290,9 @@ pm_options_read(pm_options_t *options, const char *data) { return; } + uint8_t forwarding = (uint8_t) *data++; + pm_options_scope_forwarding_set(&options->scopes[scope_index], forwarding); + for (size_t local_index = 0; local_index < locals_count; local_index++) { uint32_t local_length = pm_options_read_u32(data); data += 4; diff --git a/prism/options.h b/prism/options.h index c96fa684ac666d..05d089e34ceab9 100644 --- a/prism/options.h +++ b/prism/options.h @@ -39,8 +39,26 @@ typedef struct pm_options_scope { /** The names of the locals in the scope. */ pm_string_t *locals; + + /** Flags for the set of forwarding parameters in this scope. */ + uint8_t forwarding; } pm_options_scope_t; +/** The default value for parameters. */ +static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_NONE = 0x0; + +/** When the scope is fowarding with the * parameter. */ +static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS = 0x1; + +/** When the scope is fowarding with the ** parameter. */ +static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS = 0x2; + +/** When the scope is fowarding with the & parameter. */ +static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_BLOCK = 0x4; + +/** When the scope is fowarding with the ... parameter. */ +static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_ALL = 0x8; + // Forward declaration needed by the callback typedef. struct pm_options; @@ -319,6 +337,14 @@ PRISM_EXPORTED_FUNCTION bool pm_options_scope_init(pm_options_scope_t *scope, si */ PRISM_EXPORTED_FUNCTION const pm_string_t * pm_options_scope_local_get(const pm_options_scope_t *scope, size_t index); +/** + * Set the forwarding option on the given scope struct. + * + * @param scope The scope struct to set the forwarding on. + * @param forwarding The forwarding value to set. + */ +PRISM_EXPORTED_FUNCTION void pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding); + /** * Free the internal memory associated with the options. * @@ -367,6 +393,7 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options); * | # bytes | field | * | ------- | -------------------------- | * | `4` | the number of locals | + * | `1` | the forwarding flags | * | ... | the locals | * * Each local is laid out as follows: diff --git a/prism/prism.c b/prism/prism.c index 36b3640559e35d..766ee30ac59689 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22373,7 +22373,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm // Scopes given from the outside are not allowed to have numbered // parameters. - parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_IMPLICIT_DISALLOWED; + parser->current_scope->parameters = ((pm_scope_parameters_t) scope->forwarding) | PM_SCOPE_PARAMETERS_IMPLICIT_DISALLOWED; for (size_t local_index = 0; local_index < scope->locals_count; local_index++) { const pm_string_t *local = pm_options_scope_local_get(scope, local_index); diff --git a/prism_compile.c b/prism_compile.c index a63bf490f59611..362aecfbb046a4 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -138,12 +138,6 @@ pm_iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int node #define PM_COMPILE_NOT_POPPED(node) \ pm_compile_node(iseq, (node), ret, false, scope_node) -#define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) -#define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) -#define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) - #define PM_NODE_START_LOCATION(parser, node) \ ((pm_node_location_t) { .line = pm_newline_list_line(&(parser)->newline_list, ((const pm_node_t *) (node))->location.start, (parser)->start_line), .node_id = ((const pm_node_t *) (node))->node_id }) diff --git a/prism_compile.h b/prism_compile.h index f18fdbf892a9a8..c032449bd65ca9 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -83,6 +83,12 @@ typedef struct { bool parsed; } pm_parse_result_t; +#define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) +#define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) +#define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) + VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath, bool load_error); VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines); diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index a226b10d947cac..62f1d99bdc855f 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -139,6 +139,11 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** inner(&) end assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10}) + + def evaled(&) + eval("inner(&)") + end + assert_equal(1, evaled{1}) end; end @@ -156,8 +161,10 @@ def test_anonymous_rest_forwarding def b(*); c(*) end def c(*a); a end def d(*); b(*, *) end + def e(*); eval("b(*)") end assert_equal([1, 2], b(1, 2)) assert_equal([1, 2, 1, 2], d(1, 2)) + assert_equal([1, 2], e(1, 2)) end; end @@ -177,10 +184,12 @@ def c(**kw); kw end def d(**); b(k: 1, **) end def e(**); b(**, k: 1) end def f(a: nil, **); b(**) end + def g(**); eval("b(**)") end assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) assert_equal({k: 3}, f(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, g(a: 1, k: 3)) end; end @@ -2010,6 +2019,7 @@ def obj1.bar(*args, **kws, &block) obj4 = obj1.clone obj5 = 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__) diff --git a/vm_eval.c b/vm_eval.c index a8c12707ec3e65..74dfd40907dbfa 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1692,12 +1692,24 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, iseq = parent; rb_encoding *encoding = rb_enc_get(src); +#define FORWARDING_POSITIONALS_CHR '*' +#define FORWARDING_POSITIONALS_STR "*" +#define FORWARDING_KEYWORDS_CHR ':' +#define FORWARDING_KEYWORDS_STR ":" +#define FORWARDING_BLOCK_CHR '&' +#define FORWARDING_BLOCK_STR "&" +#define FORWARDING_ALL_CHR '.' +#define FORWARDING_ALL_STR "." + for (int scopes_index = 0; scopes_index < scopes_count; scopes_index++) { VALUE iseq_value = (VALUE)iseq; int locals_count = ISEQ_BODY(iseq)->local_table_size; + pm_options_scope_t *options_scope = &result.options.scopes[scopes_count - scopes_index - 1]; pm_options_scope_init(options_scope, locals_count); + uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; + for (int local_index = 0; local_index < locals_count; local_index++) { pm_string_t *scope_local = &options_scope->locals[local_index]; ID local = ISEQ_BODY(iseq)->local_table[local_index]; @@ -1729,10 +1741,23 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, RB_GC_GUARD(name_obj); - pm_string_owned_init(scope_local, (uint8_t *)name_dup, length); + pm_string_owned_init(scope_local, (uint8_t *) name_dup, length); + } else if (local == idMULT) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS; + pm_string_constant_init(scope_local, FORWARDING_POSITIONALS_STR, 1); + } else if (local == idPow) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS; + pm_string_constant_init(scope_local, FORWARDING_KEYWORDS_STR, 1); + } else if (local == idAnd) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK; + pm_string_constant_init(scope_local, FORWARDING_BLOCK_STR, 1); + } else if (local == idDot3) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL; + pm_string_constant_init(scope_local, FORWARDING_ALL_STR, 1); } } + pm_options_scope_forwarding_set(options_scope, forwarding); iseq = ISEQ_BODY(iseq)->parent_iseq; /* We need to GC guard the iseq because the code above malloc memory @@ -1775,14 +1800,38 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, for (int local_index = 0; local_index < locals_count; local_index++) { const pm_string_t *scope_local = &options_scope->locals[local_index]; - pm_constant_id_t constant_id = 0; - if (pm_string_length(scope_local) > 0) { - constant_id = pm_constant_pool_insert_constant( - &result.parser.constant_pool, pm_string_source(scope_local), - pm_string_length(scope_local)); - st_insert(parent_scope->index_lookup_table, (st_data_t)constant_id, (st_data_t)local_index); + + const uint8_t *source = pm_string_source(scope_local); + size_t length = pm_string_length(scope_local); + + if (length > 0) { + if (length == 1) { + switch (*source) { + case FORWARDING_POSITIONALS_CHR: + constant_id = PM_CONSTANT_MULT; + break; + case FORWARDING_KEYWORDS_CHR: + constant_id = PM_CONSTANT_POW; + break; + case FORWARDING_BLOCK_CHR: + constant_id = PM_CONSTANT_AND; + break; + case FORWARDING_ALL_CHR: + constant_id = PM_CONSTANT_DOT3; + break; + default: + constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); + break; + } + } + else { + constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); + } + + st_insert(parent_scope->index_lookup_table, (st_data_t) constant_id, (st_data_t) local_index); } + pm_constant_id_list_append(&parent_scope->locals, constant_id); } @@ -1791,6 +1840,15 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, iseq = ISEQ_BODY(iseq)->parent_iseq; } +#undef FORWARDING_POSITIONALS_CHR +#undef FORWARDING_POSITIONALS_STR +#undef FORWARDING_KEYWORDS_CHR +#undef FORWARDING_KEYWORDS_STR +#undef FORWARDING_BLOCK_CHR +#undef FORWARDING_BLOCK_STR +#undef FORWARDING_ALL_CHR +#undef FORWARDING_ALL_STR + int error_state; iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, 0, &error_state); From eaa26b8c8c8de09daddbef131837a008963399dc Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 15 Jan 2025 22:29:57 -0500 Subject: [PATCH 45/48] Backport Bug #21118 and Bug #21043 --- prism_compile.c | 793 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 585 insertions(+), 208 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 362aecfbb046a4..c000b665382190 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -3736,260 +3736,577 @@ pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *c ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; } +/** + * Compile and return the VALUE associated with the given back reference read + * node. + */ +static inline VALUE +pm_compile_back_reference_ref(const pm_back_reference_read_node_t *node) +{ + const char *type = (const char *) (node->base.location.start + 1); + + // Since a back reference is `$`, Ruby represents the ID as an + // rb_intern on the value after the `$`. + return INT2FIX(rb_intern2(type, 1)) << 1 | 1; +} + +/** + * Compile and return the VALUE associated with the given numbered reference + * read node. + */ +static inline VALUE +pm_compile_numbered_reference_ref(const pm_numbered_reference_read_node_t *node) +{ + return INT2FIX(node->number << 1); +} + static void pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, bool in_condition, LABEL **lfinish, bool explicit_receiver) { +#define PUSH_VAL(type) (in_condition ? Qtrue : rb_iseq_defined_string(type)) + // in_condition is the same as compile.c's needstr enum defined_type dtype = DEFINED_NOT_DEFINED; const pm_node_location_t location = *node_location; switch (PM_NODE_TYPE(node)) { - case PM_ARGUMENTS_NODE: { - const pm_arguments_node_t *cast = (const pm_arguments_node_t *) node; - const pm_node_list_t *arguments = &cast->arguments; - for (size_t idx = 0; idx < arguments->size; idx++) { - const pm_node_t *argument = arguments->nodes[idx]; - pm_compile_defined_expr0(iseq, argument, node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); - - if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(location.line); - } - PUSH_INSNL(ret, location, branchunless, lfinish[1]); - } - dtype = DEFINED_TRUE; - break; - } +/* DEFINED_NIL ****************************************************************/ case PM_NIL_NODE: + // defined?(nil) + // ^^^ dtype = DEFINED_NIL; break; - case PM_PARENTHESES_NODE: { - const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node; +/* DEFINED_IVAR ***************************************************************/ + case PM_INSTANCE_VARIABLE_READ_NODE: { + // defined?(@a) + // ^^ + const pm_instance_variable_read_node_t *cast = (const pm_instance_variable_read_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); - if (cast->body == NULL) { - // If we have empty parentheses, then we want to return "nil". - dtype = DEFINED_NIL; - } - else if (PM_NODE_TYPE_P(cast->body, PM_STATEMENTS_NODE) && ((const pm_statements_node_t *) cast->body)->body.size == 1) { - // If we have a parentheses node that is wrapping a single statement - // then we want to recurse down to that statement and compile it. - pm_compile_defined_expr0(iseq, ((const pm_statements_node_t *) cast->body)->body.nodes[0], node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); - return; - } - else { - // Otherwise, we have parentheses wrapping multiple statements, in - // which case this is defined as "expression". - dtype = DEFINED_EXPR; - } + PUSH_INSN3(ret, location, definedivar, ID2SYM(name), get_ivar_ic_value(iseq, name), PUSH_VAL(DEFINED_IVAR)); - break; + return; } - case PM_SELF_NODE: - dtype = DEFINED_SELF; - break; - case PM_TRUE_NODE: - dtype = DEFINED_TRUE; - break; - case PM_FALSE_NODE: - dtype = DEFINED_FALSE; +/* DEFINED_LVAR ***************************************************************/ + case PM_LOCAL_VARIABLE_READ_NODE: + // a = 1; defined?(a) + // ^ + case PM_IT_LOCAL_VARIABLE_READ_NODE: + // 1.then { defined?(it) } + // ^^ + dtype = DEFINED_LVAR; break; - case PM_ARRAY_NODE: { - const pm_array_node_t *cast = (const pm_array_node_t *) node; - - if (cast->elements.size > 0 && !lfinish[1]) { - lfinish[1] = NEW_LABEL(location.line); - } +/* DEFINED_GVAR ***************************************************************/ + case PM_GLOBAL_VARIABLE_READ_NODE: { + // defined?($a) + // ^^ + const pm_global_variable_read_node_t *cast = (const pm_global_variable_read_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); - for (size_t index = 0; index < cast->elements.size; index++) { - pm_compile_defined_expr0(iseq, cast->elements.nodes[index], node_location, ret, popped, scope_node, true, lfinish, false); - PUSH_INSNL(ret, location, branchunless, lfinish[1]); - } + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_GVAR), ID2SYM(name), PUSH_VAL(DEFINED_GVAR)); - dtype = DEFINED_EXPR; - break; + return; } - case PM_HASH_NODE: - case PM_KEYWORD_HASH_NODE: { - const pm_node_list_t *elements; - - if (PM_NODE_TYPE_P(node, PM_HASH_NODE)) { - elements = &((const pm_hash_node_t *) node)->elements; - } - else { - elements = &((const pm_keyword_hash_node_t *) node)->elements; - } - - if (elements->size > 0 && !lfinish[1]) { - lfinish[1] = NEW_LABEL(location.line); - } +/* DEFINED_CVAR ***************************************************************/ + case PM_CLASS_VARIABLE_READ_NODE: { + // defined?(@@a) + // ^^^ + const pm_class_variable_read_node_t *cast = (const pm_class_variable_read_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); - for (size_t index = 0; index < elements->size; index++) { - const pm_node_t *element = elements->nodes[index]; + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CVAR), ID2SYM(name), PUSH_VAL(DEFINED_CVAR)); - switch (PM_NODE_TYPE(element)) { - case PM_ASSOC_NODE: { - const pm_assoc_node_t *assoc = (const pm_assoc_node_t *) element; + return; + } +/* DEFINED_CONST **************************************************************/ + case PM_CONSTANT_READ_NODE: { + // defined?(A) + // ^ + const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); - pm_compile_defined_expr0(iseq, assoc->key, node_location, ret, popped, scope_node, true, lfinish, false); - PUSH_INSNL(ret, location, branchunless, lfinish[1]); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST), ID2SYM(name), PUSH_VAL(DEFINED_CONST)); - pm_compile_defined_expr0(iseq, assoc->value, node_location, ret, popped, scope_node, true, lfinish, false); - PUSH_INSNL(ret, location, branchunless, lfinish[1]); + return; + } +/* DEFINED_YIELD **************************************************************/ + case PM_YIELD_NODE: + // defined?(yield) + // ^^^^^ + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); - break; - } - case PM_ASSOC_SPLAT_NODE: { - const pm_assoc_splat_node_t *assoc_splat = (const pm_assoc_splat_node_t *) element; + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); - pm_compile_defined_expr0(iseq, assoc_splat->value, node_location, ret, popped, scope_node, true, lfinish, false); - PUSH_INSNL(ret, location, branchunless, lfinish[1]); + return; +/* DEFINED_ZSUPER *************************************************************/ + case PM_SUPER_NODE: { + // defined?(super 1, 2) + // ^^^^^^^^^^ + const pm_super_node_t *cast = (const pm_super_node_t *) node; - break; - } - default: - rb_bug("unexpected node type in hash node: %s", pm_node_type_to_str(PM_NODE_TYPE(element))); - break; - } + if (cast->block != NULL && !PM_NODE_TYPE_P(cast->block, PM_BLOCK_ARGUMENT_NODE)) { + dtype = DEFINED_EXPR; + break; } - dtype = DEFINED_EXPR; - break; + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_ZSUPER), 0, PUSH_VAL(DEFINED_ZSUPER)); + return; } - case PM_SPLAT_NODE: { - const pm_splat_node_t *cast = (const pm_splat_node_t *) node; - pm_compile_defined_expr0(iseq, cast->expression, node_location, ret, popped, scope_node, in_condition, lfinish, false); + case PM_FORWARDING_SUPER_NODE: { + // defined?(super) + // ^^^^^ + const pm_forwarding_super_node_t *cast = (const pm_forwarding_super_node_t *) node; - if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(location.line); + if (cast->block != NULL) { + dtype = DEFINED_EXPR; + break; } - PUSH_INSNL(ret, location, branchunless, lfinish[1]); - dtype = DEFINED_EXPR; - break; - } - case PM_IMPLICIT_NODE: { - const pm_implicit_node_t *cast = (const pm_implicit_node_t *) node; - pm_compile_defined_expr0(iseq, cast->value, node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_ZSUPER), 0, PUSH_VAL(DEFINED_ZSUPER)); return; } +/* DEFINED_SELF ***************************************************************/ + case PM_SELF_NODE: + // defined?(self) + // ^^^^ + dtype = DEFINED_SELF; + break; +/* DEFINED_TRUE ***************************************************************/ + case PM_TRUE_NODE: + // defined?(true) + // ^^^^ + dtype = DEFINED_TRUE; + break; +/* DEFINED_FALSE **************************************************************/ + case PM_FALSE_NODE: + // defined?(false) + // ^^^^^ + dtype = DEFINED_FALSE; + break; +/* DEFINED_ASGN ***************************************************************/ + case PM_CALL_AND_WRITE_NODE: + // defined?(a.a &&= 1) + // ^^^^^^^^^ + case PM_CALL_OPERATOR_WRITE_NODE: + // defined?(a.a += 1) + // ^^^^^^^^ + case PM_CALL_OR_WRITE_NODE: + // defined?(a.a ||= 1) + // ^^^^^^^^^ + case PM_CLASS_VARIABLE_AND_WRITE_NODE: + // defined?(@@a &&= 1) + // ^^^^^^^^^ + case PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE: + // defined?(@@a += 1) + // ^^^^^^^^ + case PM_CLASS_VARIABLE_OR_WRITE_NODE: + // defined?(@@a ||= 1) + // ^^^^^^^^^ + case PM_CLASS_VARIABLE_WRITE_NODE: + // defined?(@@a = 1) + // ^^^^^^^ + case PM_CONSTANT_AND_WRITE_NODE: + // defined?(A &&= 1) + // ^^^^^^^ + case PM_CONSTANT_OPERATOR_WRITE_NODE: + // defined?(A += 1) + // ^^^^^^ + case PM_CONSTANT_OR_WRITE_NODE: + // defined?(A ||= 1) + // ^^^^^^^ + case PM_CONSTANT_PATH_AND_WRITE_NODE: + // defined?(A::A &&= 1) + // ^^^^^^^^^^ + case PM_CONSTANT_PATH_OPERATOR_WRITE_NODE: + // defined?(A::A += 1) + // ^^^^^^^^^ + case PM_CONSTANT_PATH_OR_WRITE_NODE: + // defined?(A::A ||= 1) + // ^^^^^^^^^^ + case PM_CONSTANT_PATH_WRITE_NODE: + // defined?(A::A = 1) + // ^^^^^^^^ + case PM_CONSTANT_WRITE_NODE: + // defined?(A = 1) + // ^^^^^ + case PM_GLOBAL_VARIABLE_AND_WRITE_NODE: + // defined?($a &&= 1) + // ^^^^^^^^ + case PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE: + // defined?($a += 1) + // ^^^^^^^ + case PM_GLOBAL_VARIABLE_OR_WRITE_NODE: + // defined?($a ||= 1) + // ^^^^^^^^ + case PM_GLOBAL_VARIABLE_WRITE_NODE: + // defined?($a = 1) + // ^^^^^^ + case PM_INDEX_AND_WRITE_NODE: + // defined?(a[1] &&= 1) + // ^^^^^^^^^^ + case PM_INDEX_OPERATOR_WRITE_NODE: + // defined?(a[1] += 1) + // ^^^^^^^^^ + case PM_INDEX_OR_WRITE_NODE: + // defined?(a[1] ||= 1) + // ^^^^^^^^^^ + case PM_INSTANCE_VARIABLE_AND_WRITE_NODE: + // defined?(@a &&= 1) + // ^^^^^^^^ + case PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE: + // defined?(@a += 1) + // ^^^^^^^ + case PM_INSTANCE_VARIABLE_OR_WRITE_NODE: + // defined?(@a ||= 1) + // ^^^^^^^^ + case PM_INSTANCE_VARIABLE_WRITE_NODE: + // defined?(@a = 1) + // ^^^^^^ + case PM_LOCAL_VARIABLE_AND_WRITE_NODE: + // defined?(a &&= 1) + // ^^^^^^^ + case PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE: + // defined?(a += 1) + // ^^^^^^ + case PM_LOCAL_VARIABLE_OR_WRITE_NODE: + // defined?(a ||= 1) + // ^^^^^^^ + case PM_LOCAL_VARIABLE_WRITE_NODE: + // defined?(a = 1) + // ^^^^^ + case PM_MULTI_WRITE_NODE: + // defined?((a, = 1)) + // ^^^^^^ + dtype = DEFINED_ASGN; + break; +/* DEFINED_EXPR ***************************************************************/ + case PM_ALIAS_GLOBAL_VARIABLE_NODE: + // defined?((alias $a $b)) + // ^^^^^^^^^^^ + case PM_ALIAS_METHOD_NODE: + // defined?((alias a b)) + // ^^^^^^^^^ case PM_AND_NODE: - case PM_BEGIN_NODE: + // defined?(a and b) + // ^^^^^^^ case PM_BREAK_NODE: - case PM_CASE_NODE: + // defined?(break 1) + // ^^^^^^^ case PM_CASE_MATCH_NODE: + // defined?(case 1; in 1; end) + // ^^^^^^^^^^^^^^^^^ + case PM_CASE_NODE: + // defined?(case 1; when 1; end) + // ^^^^^^^^^^^^^^^^^^^ case PM_CLASS_NODE: + // defined?(class Foo; end) + // ^^^^^^^^^^^^^^ case PM_DEF_NODE: + // defined?(def a() end) + // ^^^^^^^^^^^ case PM_DEFINED_NODE: + // defined?(defined?(a)) + // ^^^^^^^^^^^ + case PM_FLIP_FLOP_NODE: + // defined?(not (a .. b)) + // ^^^^^^ case PM_FLOAT_NODE: + // defined?(1.0) + // ^^^ case PM_FOR_NODE: + // defined?(for a in 1 do end) + // ^^^^^^^^^^^^^^^^^ case PM_IF_NODE: + // defined?(if a then end) + // ^^^^^^^^^^^^^ case PM_IMAGINARY_NODE: + // defined?(1i) + // ^^ case PM_INTEGER_NODE: + // defined?(1) + // ^ + case PM_INTERPOLATED_MATCH_LAST_LINE_NODE: + // defined?(not /#{1}/) + // ^^^^^^ case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: + // defined?(/#{1}/) + // ^^^^^^ case PM_INTERPOLATED_STRING_NODE: + // defined?("#{1}") + // ^^^^^^ case PM_INTERPOLATED_SYMBOL_NODE: + // defined?(:"#{1}") + // ^^^^^^^ case PM_INTERPOLATED_X_STRING_NODE: + // defined?(`#{1}`) + // ^^^^^^ case PM_LAMBDA_NODE: + // defined?(-> {}) + // ^^^^^ + case PM_MATCH_LAST_LINE_NODE: + // defined?(not //) + // ^^^^^^ case PM_MATCH_PREDICATE_NODE: + // defined?(1 in 1) + // ^^^^^^ case PM_MATCH_REQUIRED_NODE: + // defined?(1 => 1) + // ^^^^^^ case PM_MATCH_WRITE_NODE: + // defined?(/(?)/ =~ "") + // ^^^^^^^^^^^^^^ case PM_MODULE_NODE: + // defined?(module A end) + // ^^^^^^^^^^^^ case PM_NEXT_NODE: + // defined?(next 1) + // ^^^^^^ case PM_OR_NODE: + // defined?(a or b) + // ^^^^^^ + case PM_POST_EXECUTION_NODE: + // defined?((END {})) + // ^^^^^^^^ case PM_RANGE_NODE: + // defined?(1..1) + // ^^^^ case PM_RATIONAL_NODE: + // defined?(1r) + // ^^ case PM_REDO_NODE: + // defined?(redo) + // ^^^^ case PM_REGULAR_EXPRESSION_NODE: + // defined?(//) + // ^^ + case PM_RESCUE_MODIFIER_NODE: + // defined?(a rescue b) + // ^^^^^^^^^^ case PM_RETRY_NODE: + // defined?(retry) + // ^^^^^ case PM_RETURN_NODE: + // defined?(return) + // ^^^^^^ case PM_SINGLETON_CLASS_NODE: + // defined?(class << self; end) + // ^^^^^^^^^^^^^^^^^^ case PM_SOURCE_ENCODING_NODE: + // defined?(__ENCODING__) + // ^^^^^^^^^^^^ case PM_SOURCE_FILE_NODE: + // defined?(__FILE__) + // ^^^^^^^^ case PM_SOURCE_LINE_NODE: + // defined?(__LINE__) + // ^^^^^^^^ case PM_STRING_NODE: + // defined?("") + // ^^ case PM_SYMBOL_NODE: + // defined?(:a) + // ^^ + case PM_UNDEF_NODE: + // defined?((undef a)) + // ^^^^^^^ case PM_UNLESS_NODE: + // defined?(unless a then end) + // ^^^^^^^^^^^^^^^^^ case PM_UNTIL_NODE: + // defined?(until a do end) + // ^^^^^^^^^^^^^^ case PM_WHILE_NODE: + // defined?(while a do end) + // ^^^^^^^^^^^^^^ case PM_X_STRING_NODE: + // defined?(``) + // ^^ dtype = DEFINED_EXPR; break; - case PM_LOCAL_VARIABLE_READ_NODE: - dtype = DEFINED_LVAR; - break; - -#define PUSH_VAL(type) (in_condition ? Qtrue : rb_iseq_defined_string(type)) - - case PM_INSTANCE_VARIABLE_READ_NODE: { - const pm_instance_variable_read_node_t *cast = (const pm_instance_variable_read_node_t *) node; - - ID name = pm_constant_id_lookup(scope_node, cast->name); - PUSH_INSN3(ret, location, definedivar, ID2SYM(name), get_ivar_ic_value(iseq, name), PUSH_VAL(DEFINED_IVAR)); - - return; - } +/* DEFINED_REF ****************************************************************/ case PM_BACK_REFERENCE_READ_NODE: { - const char *char_ptr = (const char *) (node->location.start + 1); - ID backref_val = INT2FIX(rb_intern2(char_ptr, 1)) << 1 | 1; + // defined?($+) + // ^^ + const pm_back_reference_read_node_t *cast = (const pm_back_reference_read_node_t *) node; + VALUE ref = pm_compile_back_reference_ref(cast); PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_REF), backref_val, PUSH_VAL(DEFINED_GVAR)); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_REF), ref, PUSH_VAL(DEFINED_GVAR)); return; } case PM_NUMBERED_REFERENCE_READ_NODE: { - uint32_t reference_number = ((const pm_numbered_reference_read_node_t *) node)->number; + // defined?($1) + // ^^ + const pm_numbered_reference_read_node_t *cast = (const pm_numbered_reference_read_node_t *) node; + VALUE ref = pm_compile_numbered_reference_ref(cast); PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_REF), INT2FIX(reference_number << 1), PUSH_VAL(DEFINED_GVAR)); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_REF), ref, PUSH_VAL(DEFINED_GVAR)); return; } - case PM_GLOBAL_VARIABLE_READ_NODE: { - const pm_global_variable_read_node_t *cast = (const pm_global_variable_read_node_t *) node; - VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); +/* DEFINED_CONST_FROM *********************************************************/ + case PM_CONSTANT_PATH_NODE: { + // defined?(A::A) + // ^^^^ + const pm_constant_path_node_t *cast = (const pm_constant_path_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); - PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_GVAR), name, PUSH_VAL(DEFINED_GVAR)); + if (cast->parent != NULL) { + if (!lfinish[1]) lfinish[1] = NEW_LABEL(location.line); + pm_compile_defined_expr0(iseq, cast->parent, node_location, ret, popped, scope_node, true, lfinish, false); + + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + PM_COMPILE(cast->parent); + } + else { + PUSH_INSN1(ret, location, putobject, rb_cObject); + } + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST_FROM), ID2SYM(name), PUSH_VAL(DEFINED_CONST)); return; } - case PM_CLASS_VARIABLE_READ_NODE: { - const pm_class_variable_read_node_t *cast = (const pm_class_variable_read_node_t *) node; - VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); +/* Containers *****************************************************************/ + case PM_BEGIN_NODE: { + // defined?(begin end) + // ^^^^^^^^^ + const pm_begin_node_t *cast = (const pm_begin_node_t *) node; - PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CVAR), name, PUSH_VAL(DEFINED_CVAR)); + if (cast->rescue_clause == NULL && cast->ensure_clause == NULL && cast->else_clause == NULL) { + if (cast->statements == NULL) { + // If we have empty statements, then we want to return "nil". + dtype = DEFINED_NIL; + } + else if (cast->statements->body.size == 1) { + // If we have a begin node that is wrapping a single statement + // then we want to recurse down to that statement and compile + // it. + pm_compile_defined_expr0(iseq, cast->statements->body.nodes[0], node_location, ret, popped, scope_node, in_condition, lfinish, false); + return; + } + else { + // Otherwise, we have a begin wrapping multiple statements, in + // which case this is defined as "expression". + dtype = DEFINED_EXPR; + } + } else { + // If we have any of the other clauses besides the main begin/end, + // this is defined as "expression". + dtype = DEFINED_EXPR; + } - return; + break; } - case PM_CONSTANT_READ_NODE: { - const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; - VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + case PM_PARENTHESES_NODE: { + // defined?(()) + // ^^ + const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node; - PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST), name, PUSH_VAL(DEFINED_CONST)); + if (cast->body == NULL) { + // If we have empty parentheses, then we want to return "nil". + dtype = DEFINED_NIL; + } + else if (PM_NODE_TYPE_P(cast->body, PM_STATEMENTS_NODE) && ((const pm_statements_node_t *) cast->body)->body.size == 1) { + // If we have a parentheses node that is wrapping a single statement + // then we want to recurse down to that statement and compile it. + pm_compile_defined_expr0(iseq, ((const pm_statements_node_t *) cast->body)->body.nodes[0], node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); + return; + } + else { + // Otherwise, we have parentheses wrapping multiple statements, in + // which case this is defined as "expression". + dtype = DEFINED_EXPR; + } - return; + break; } - case PM_CONSTANT_PATH_NODE: { - const pm_constant_path_node_t *cast = (const pm_constant_path_node_t *) node; - VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + case PM_ARRAY_NODE: { + // defined?([]) + // ^^ + const pm_array_node_t *cast = (const pm_array_node_t *) node; - if (cast->parent != NULL) { - if (!lfinish[1]) lfinish[1] = NEW_LABEL(location.line); - pm_compile_defined_expr0(iseq, cast->parent, node_location, ret, popped, scope_node, true, lfinish, false); + if (cast->elements.size > 0 && !lfinish[1]) { + lfinish[1] = NEW_LABEL(location.line); + } + for (size_t index = 0; index < cast->elements.size; index++) { + pm_compile_defined_expr0(iseq, cast->elements.nodes[index], node_location, ret, popped, scope_node, true, lfinish, false); PUSH_INSNL(ret, location, branchunless, lfinish[1]); - PM_COMPILE(cast->parent); + } + + dtype = DEFINED_EXPR; + break; + } + case PM_HASH_NODE: + // defined?({ a: 1 }) + // ^^^^^^^^ + case PM_KEYWORD_HASH_NODE: { + // defined?(a(a: 1)) + // ^^^^ + const pm_node_list_t *elements; + + if (PM_NODE_TYPE_P(node, PM_HASH_NODE)) { + elements = &((const pm_hash_node_t *) node)->elements; } else { - PUSH_INSN1(ret, location, putobject, rb_cObject); + elements = &((const pm_keyword_hash_node_t *) node)->elements; + } + + if (elements->size > 0 && !lfinish[1]) { + lfinish[1] = NEW_LABEL(location.line); + } + + for (size_t index = 0; index < elements->size; index++) { + pm_compile_defined_expr0(iseq, elements->nodes[index], node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + } + + dtype = DEFINED_EXPR; + break; + } + case PM_ASSOC_NODE: { + // defined?({ a: 1 }) + // ^^^^ + const pm_assoc_node_t *cast = (const pm_assoc_node_t *) node; + + pm_compile_defined_expr0(iseq, cast->key, node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + pm_compile_defined_expr0(iseq, cast->value, node_location, ret, popped, scope_node, true, lfinish, false); + + return; + } + case PM_ASSOC_SPLAT_NODE: { + // defined?({ **a }) + // ^^^^ + const pm_assoc_splat_node_t *cast = (const pm_assoc_splat_node_t *) node; + + if (cast->value == NULL) { + dtype = DEFINED_EXPR; + break; } - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST_FROM), name, PUSH_VAL(DEFINED_CONST)); + pm_compile_defined_expr0(iseq, cast->value, node_location, ret, popped, scope_node, true, lfinish, false); + return; + } + case PM_IMPLICIT_NODE: { + // defined?({ a: }) + // ^^ + const pm_implicit_node_t *cast = (const pm_implicit_node_t *) node; + pm_compile_defined_expr0(iseq, cast->value, node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); return; } case PM_CALL_NODE: { +#define BLOCK_P(cast) ((cast)->block != NULL && PM_NODE_TYPE_P((cast)->block, PM_BLOCK_NODE)) + + // defined?(a(1, 2, 3)) + // ^^^^^^^^^^ const pm_call_node_t *cast = ((const pm_call_node_t *) node); if (cast->block != NULL && PM_NODE_TYPE_P(cast->block, PM_BLOCK_NODE)) { @@ -3997,9 +4314,7 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l break; } - ID method_id = pm_constant_id_lookup(scope_node, cast->name); - - if (cast->receiver || cast->arguments) { + if (cast->receiver || cast->arguments || (cast->block && PM_NODE_TYPE_P(cast->block, PM_BLOCK_ARGUMENT_NODE))) { if (!lfinish[1]) lfinish[1] = NEW_LABEL(location.line); if (!lfinish[2]) lfinish[2] = NEW_LABEL(location.line); } @@ -4009,89 +4324,151 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l PUSH_INSNL(ret, location, branchunless, lfinish[1]); } - if (cast->receiver) { - pm_compile_defined_expr0(iseq, cast->receiver, node_location, ret, popped, scope_node, true, lfinish, true); + if (cast->block && PM_NODE_TYPE_P(cast->block, PM_BLOCK_ARGUMENT_NODE)) { + pm_compile_defined_expr0(iseq, cast->block, node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + } - if (PM_NODE_TYPE_P(cast->receiver, PM_CALL_NODE)) { + if (cast->receiver) { + if (PM_NODE_TYPE_P(cast->receiver, PM_CALL_NODE) && !BLOCK_P((const pm_call_node_t *) cast->receiver)) { + // Special behavior here where we chain calls together. This is + // the only path that sets explicit_receiver to true. + pm_compile_defined_expr0(iseq, cast->receiver, node_location, ret, popped, scope_node, true, lfinish, true); PUSH_INSNL(ret, location, branchunless, lfinish[2]); const pm_call_node_t *receiver = (const pm_call_node_t *) cast->receiver; ID method_id = pm_constant_id_lookup(scope_node, receiver->name); + pm_compile_call(iseq, receiver, ret, popped, scope_node, method_id, NULL); } else { + pm_compile_defined_expr0(iseq, cast->receiver, node_location, ret, popped, scope_node, true, lfinish, false); PUSH_INSNL(ret, location, branchunless, lfinish[1]); PM_COMPILE(cast->receiver); } + ID method_id = pm_constant_id_lookup(scope_node, cast->name); + if (explicit_receiver) PUSH_INSN(ret, location, dup); PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_METHOD), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD)); } else { + ID method_id = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN(ret, location, putself); if (explicit_receiver) PUSH_INSN(ret, location, dup); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_FUNC), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD)); } return; } - case PM_YIELD_NODE: - PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); - iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); - return; - case PM_SUPER_NODE: - case PM_FORWARDING_SUPER_NODE: - PUSH_INSN(ret, location, putnil); - PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_ZSUPER), 0, PUSH_VAL(DEFINED_ZSUPER)); - return; - case PM_CALL_AND_WRITE_NODE: - case PM_CALL_OPERATOR_WRITE_NODE: - case PM_CALL_OR_WRITE_NODE: - - case PM_CONSTANT_WRITE_NODE: - case PM_CONSTANT_OPERATOR_WRITE_NODE: - case PM_CONSTANT_AND_WRITE_NODE: - case PM_CONSTANT_OR_WRITE_NODE: + case PM_ARGUMENTS_NODE: { + // defined?(a(1, 2, 3)) + // ^^^^^^^ + const pm_arguments_node_t *cast = (const pm_arguments_node_t *) node; - case PM_CONSTANT_PATH_AND_WRITE_NODE: - case PM_CONSTANT_PATH_OPERATOR_WRITE_NODE: - case PM_CONSTANT_PATH_OR_WRITE_NODE: - case PM_CONSTANT_PATH_WRITE_NODE: + for (size_t index = 0; index < cast->arguments.size; index++) { + pm_compile_defined_expr0(iseq, cast->arguments.nodes[index], node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); - case PM_GLOBAL_VARIABLE_WRITE_NODE: - case PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE: - case PM_GLOBAL_VARIABLE_AND_WRITE_NODE: - case PM_GLOBAL_VARIABLE_OR_WRITE_NODE: + if (!lfinish[1]) { + lfinish[1] = NEW_LABEL(location.line); + } + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + } - case PM_CLASS_VARIABLE_WRITE_NODE: - case PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE: - case PM_CLASS_VARIABLE_AND_WRITE_NODE: - case PM_CLASS_VARIABLE_OR_WRITE_NODE: + dtype = DEFINED_TRUE; + break; + } + case PM_BLOCK_ARGUMENT_NODE: + // defined?(a(&b)) + // ^^ + dtype = DEFINED_EXPR; + break; + case PM_FORWARDING_ARGUMENTS_NODE: + // def a(...) = defined?(a(...)) + // ^^^ + dtype = DEFINED_EXPR; + break; + case PM_SPLAT_NODE: { + // def a(*) = defined?(a(*)) + // ^ + const pm_splat_node_t *cast = (const pm_splat_node_t *) node; - case PM_INDEX_AND_WRITE_NODE: - case PM_INDEX_OPERATOR_WRITE_NODE: - case PM_INDEX_OR_WRITE_NODE: + if (cast->expression == NULL) { + dtype = DEFINED_EXPR; + break; + } - case PM_INSTANCE_VARIABLE_WRITE_NODE: - case PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE: - case PM_INSTANCE_VARIABLE_AND_WRITE_NODE: - case PM_INSTANCE_VARIABLE_OR_WRITE_NODE: + pm_compile_defined_expr0(iseq, cast->expression, node_location, ret, popped, scope_node, in_condition, lfinish, false); - case PM_LOCAL_VARIABLE_WRITE_NODE: - case PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE: - case PM_LOCAL_VARIABLE_AND_WRITE_NODE: - case PM_LOCAL_VARIABLE_OR_WRITE_NODE: + if (!lfinish[1]) lfinish[1] = NEW_LABEL(location.line); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); - case PM_MULTI_WRITE_NODE: - dtype = DEFINED_ASGN; + dtype = DEFINED_EXPR; break; - default: - rb_bug("Unsupported node %s", pm_node_type_to_str(PM_NODE_TYPE(node))); + } + case PM_SHAREABLE_CONSTANT_NODE: + // # shareable_constant_value: literal + // defined?(A = 1) + // ^^^^^ + pm_compile_defined_expr0(iseq, ((const pm_shareable_constant_node_t *) node)->write, node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); + return; +/* Unreachable (parameters) ***************************************************/ + case PM_BLOCK_LOCAL_VARIABLE_NODE: + case PM_BLOCK_PARAMETER_NODE: + case PM_BLOCK_PARAMETERS_NODE: + case PM_FORWARDING_PARAMETER_NODE: + case PM_IMPLICIT_REST_NODE: + case PM_IT_PARAMETERS_NODE: + case PM_PARAMETERS_NODE: + case PM_KEYWORD_REST_PARAMETER_NODE: + case PM_NO_KEYWORDS_PARAMETER_NODE: + case PM_NUMBERED_PARAMETERS_NODE: + case PM_OPTIONAL_KEYWORD_PARAMETER_NODE: + case PM_OPTIONAL_PARAMETER_NODE: + case PM_REQUIRED_KEYWORD_PARAMETER_NODE: + case PM_REQUIRED_PARAMETER_NODE: + case PM_REST_PARAMETER_NODE: +/* Unreachable (pattern matching) *********************************************/ + case PM_ALTERNATION_PATTERN_NODE: + case PM_ARRAY_PATTERN_NODE: + case PM_CAPTURE_PATTERN_NODE: + case PM_FIND_PATTERN_NODE: + case PM_HASH_PATTERN_NODE: + case PM_PINNED_EXPRESSION_NODE: + case PM_PINNED_VARIABLE_NODE: +/* Unreachable (indirect writes) **********************************************/ + case PM_CALL_TARGET_NODE: + case PM_CLASS_VARIABLE_TARGET_NODE: + case PM_CONSTANT_PATH_TARGET_NODE: + case PM_CONSTANT_TARGET_NODE: + case PM_GLOBAL_VARIABLE_TARGET_NODE: + case PM_INDEX_TARGET_NODE: + case PM_INSTANCE_VARIABLE_TARGET_NODE: + case PM_LOCAL_VARIABLE_TARGET_NODE: + case PM_MULTI_TARGET_NODE: +/* Unreachable (clauses) ******************************************************/ + case PM_ELSE_NODE: + case PM_ENSURE_NODE: + case PM_IN_NODE: + case PM_RESCUE_NODE: + case PM_WHEN_NODE: +/* Unreachable (miscellaneous) ************************************************/ + case PM_BLOCK_NODE: + case PM_EMBEDDED_STATEMENTS_NODE: + case PM_EMBEDDED_VARIABLE_NODE: + case PM_MISSING_NODE: + case PM_PRE_EXECUTION_NODE: + case PM_PROGRAM_NODE: + case PM_SCOPE_NODE: + case PM_STATEMENTS_NODE: + rb_bug("Unreachable node in defined?: %s", pm_node_type_to_str(PM_NODE_TYPE(node))); } RUBY_ASSERT(dtype != DEFINED_NOT_DEFINED); PUSH_INSN1(ret, location, putobject, PUSH_VAL(dtype)); + #undef PUSH_VAL } @@ -8306,11 +8683,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // $+ // ^^ if (!popped) { - // Since a back reference is `$`, ruby represents the ID as the - // an rb_intern on the value after the `$`. - char *char_ptr = (char *)(node->location.start) + 1; - ID backref_val = INT2FIX(rb_intern2(char_ptr, 1)) << 1 | 1; - PUSH_INSN2(ret, location, getspecial, INT2FIX(1), backref_val); + const pm_back_reference_read_node_t *cast = (const pm_back_reference_read_node_t *) node; + VALUE backref = pm_compile_back_reference_ref(cast); + + PUSH_INSN2(ret, location, getspecial, INT2FIX(1), backref); } return; } @@ -9520,10 +9896,11 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // $1 // ^^ if (!popped) { - uint32_t reference_number = ((const pm_numbered_reference_read_node_t *) node)->number; + const pm_numbered_reference_read_node_t *cast = (const pm_numbered_reference_read_node_t *) node; - if (reference_number > 0) { - PUSH_INSN2(ret, location, getspecial, INT2FIX(1), INT2FIX(reference_number << 1)); + if (cast->number != 0) { + VALUE ref = pm_compile_numbered_reference_ref(cast); + PUSH_INSN2(ret, location, getspecial, INT2FIX(1), ref); } else { PUSH_INSN(ret, location, putnil); From 62f736f23e9099c5aa29975dc7fbe2fe8c40b5b2 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 14 Feb 2025 13:15:57 -0500 Subject: [PATCH 46/48] Backport Bug #21137 --- prism/prism.c | 38 ++++++++++++++++++++-- test/prism/fixtures/it_indirect_writes.txt | 23 +++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 test/prism/fixtures/it_indirect_writes.txt diff --git a/prism/prism.c b/prism/prism.c index 766ee30ac59689..38c95138ca59d8 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5653,8 +5653,7 @@ pm_lambda_node_create( */ static pm_local_variable_and_write_node_t * pm_local_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value, pm_constant_id_t name, uint32_t depth) { - assert(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_CALL_NODE)); - assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); + assert(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_IT_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_CALL_NODE)); assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); pm_local_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_and_write_node_t); *node = (pm_local_variable_and_write_node_t) { @@ -5708,7 +5707,7 @@ pm_local_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *tar */ static pm_local_variable_or_write_node_t * pm_local_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value, pm_constant_id_t name, uint32_t depth) { - assert(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_CALL_NODE)); + assert(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_IT_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_CALL_NODE)); assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); pm_local_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_or_write_node_t); @@ -21066,6 +21065,17 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_destroy(parser, node); return result; } + case PM_IT_LOCAL_VARIABLE_READ_NODE: { + pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); + parser_lex(parser); + + pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); + pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, node, &token, value, name, 0); + + parse_target_implicit_parameter(parser, node); + pm_node_destroy(parser, node); + return result; + } case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); @@ -21189,6 +21199,17 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_destroy(parser, node); return result; } + case PM_IT_LOCAL_VARIABLE_READ_NODE: { + pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); + parser_lex(parser); + + pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); + pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, node, &token, value, name, 0); + + parse_target_implicit_parameter(parser, node); + pm_node_destroy(parser, node); + return result; + } case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); @@ -21322,6 +21343,17 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_destroy(parser, node); return result; } + case PM_IT_LOCAL_VARIABLE_READ_NODE: { + pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); + parser_lex(parser); + + pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); + pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, node, &token, value, name, 0); + + parse_target_implicit_parameter(parser, node); + pm_node_destroy(parser, node); + return result; + } case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); diff --git a/test/prism/fixtures/it_indirect_writes.txt b/test/prism/fixtures/it_indirect_writes.txt new file mode 100644 index 00000000000000..bb87e9483e2a75 --- /dev/null +++ b/test/prism/fixtures/it_indirect_writes.txt @@ -0,0 +1,23 @@ +tap { it += 1 } + +tap { it ||= 1 } + +tap { it &&= 1 } + +tap { it; it += 1 } + +tap { it; it ||= 1 } + +tap { it; it &&= 1 } + +tap { it += 1; it } + +tap { it ||= 1; it } + +tap { it &&= 1; it } + +tap { it; it += 1; it } + +tap { it; it ||= 1; it } + +tap { it; it &&= 1; it } From d5f42a07b26d4430c4c51ec3bb5bc482c68e9aca Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 14 Feb 2025 14:20:11 -0500 Subject: [PATCH 47/48] Fix locals_test based on previous backports --- test/prism/locals_test.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 2c0036289cdabf..e0e9a458559759 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -140,14 +140,17 @@ def prism_locals(source) case node when BlockNode, DefNode, LambdaNode names = node.locals - params = - if node.is_a?(DefNode) - node.parameters - elsif node.parameters.is_a?(NumberedParametersNode) - nil - else - node.parameters&.parameters - end + params = nil + + if node.is_a?(DefNode) + params = node.parameters + elsif node.parameters.is_a?(NumberedParametersNode) + # nothing + elsif node.parameters.is_a?(ItParametersNode) + names.unshift(AnonymousLocal) + else + params = node.parameters&.parameters + end # prism places parameters in the same order that they appear in the # source. CRuby places them in the order that they need to appear From d2930f8e7a5db8a7337fa43370940381b420cc3e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 14 Feb 2025 13:25:54 -0800 Subject: [PATCH 48/48] v3.4.2 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 8e60c598aadb99..3e9884565677e6 100644 --- a/version.h +++ b/version.h @@ -9,7 +9,7 @@ */ # define RUBY_VERSION_MAJOR RUBY_API_VERSION_MAJOR # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR -#define RUBY_VERSION_TEENY 1 +#define RUBY_VERSION_TEENY 2 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR #define RUBY_PATCHLEVEL 28