From 95c041bd3bdf0af0be00ed8546947dc73dc92897 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 12 May 2026 08:49:34 +0200 Subject: [PATCH 1/4] [ruby/json] generator.c: Handle stupidly large depth https://github.com/ruby/json/commit/1d32bd4596 --- ext/json/fbuffer/fbuffer.h | 11 ++++++++++- test/json/json_generator_test.rb | 11 +++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index 9660e70dc76529..b84a073554cf8c 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -131,6 +131,15 @@ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested) } } +static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b) +{ + size_t result = a * b; + if (RB_UNLIKELY(a != 0 && (result / a) != b)) { + rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated"); + } + return result; +} + static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len) { MEMCPY(fb->ptr + fb->len, newstr, char, len); @@ -175,7 +184,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) size_t len; RSTRING_GETMEM(str, ptr, len); - fbuffer_inc_capa(fb, repeat * len); + fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len)); while (repeat) { #if JSON_DEBUG fb->requested = len; diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 1cfd19da1228e7..87d9cd7f7deeb5 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -1081,4 +1081,15 @@ def test_negative_depth_raises end end + def test_large_depth_raises + assert_raise(RangeError, ArgumentError) do + JSON.generate([[1]], + indent: " " * 5, + array_nl: "\n", + depth: 3_689_348_814_741_910_324, + max_nesting: 0 + ) + end + end + end From 803b3169e0a8533cb870c995029d50cba190c322 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 14 Nov 2025 16:21:00 +0900 Subject: [PATCH 2/4] Show "END in method" warning immediately --- parse.y | 6 +++--- test/ruby/test_beginendblock.rb | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/parse.y b/parse.y index a92faab27fad51..d21abbe7e4c22a 100644 --- a/parse.y +++ b/parse.y @@ -3296,6 +3296,9 @@ allow_exits : {$$ = allow_block_exit(p);}; k_END : keyword_END lex_ctxt { + if (p->ctxt.in_def) { + rb_warn0("END in method; use at_exit"); + } $$ = $2; p->ctxt.in_rescue = before_rescue; /*% ripper: $:2 %*/ @@ -3380,9 +3383,6 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit } | k_END[k_end] allow_exits[allow] '{'[lbrace] compstmt(stmts)[body] '}'[rbrace] { - if (p->ctxt.in_def) { - rb_warn0("END in method; use at_exit"); - } restore_block_exit(p, $allow); p->ctxt = $k_end; { diff --git a/test/ruby/test_beginendblock.rb b/test/ruby/test_beginendblock.rb index 3706efab52dd0b..74da11abf40c53 100644 --- a/test/ruby/test_beginendblock.rb +++ b/test/ruby/test_beginendblock.rb @@ -40,7 +40,8 @@ def test_endblockwarn assert_in_out_err([], "#{<<~"begin;"}#{<<~'end;'}", [], ['-:2: warning: END in method; use at_exit']) begin; def end1 - END {} + END { + } end end; end From 2f9432d2114833b105577ad72e323990daf5101e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 14 Nov 2025 17:38:59 +0900 Subject: [PATCH 3/4] [Bug #20409] Make `break` and `redo` in `END` syntax error --- parse.y | 37 ++++++++++++++++++++++++++++--------- test/ruby/test_ast.rb | 6 +++++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/parse.y b/parse.y index d21abbe7e4c22a..d163d2a05023fb 100644 --- a/parse.y +++ b/parse.y @@ -1659,7 +1659,22 @@ static NODE *add_block_exit(struct parser_params *p, NODE *node); static rb_node_exits_t *init_block_exit(struct parser_params *p); static rb_node_exits_t *allow_block_exit(struct parser_params *p); static void restore_block_exit(struct parser_params *p, rb_node_exits_t *exits); -static void clear_block_exit(struct parser_params *p, bool error); +static void clear_block_exit(struct parser_params *p, unsigned int error_mask); + +static unsigned int +exits_mask(enum node_type t) +{ + switch (t) { + case NODE_BREAK: + case NODE_NEXT: + case NODE_REDO: + return 1u << (t - NODE_BREAK); + default: + UNREACHABLE_RETURN(0); + } +} + +#define EXITS_MASK_ALL (exits_mask(NODE_BREAK)|exits_mask(NODE_NEXT)|exits_mask(NODE_REDO)) static void next_rescue_context(struct lex_context *next, const struct lex_context *outer, enum rescue_context def) @@ -1677,7 +1692,7 @@ restore_defun(struct parser_params *p, rb_node_def_temp_t *temp) p->ctxt.in_rescue = ctxt.in_rescue; p->max_numparam = temp->save.max_numparam; numparam_pop(p, temp->save.numparam_save); - clear_block_exit(p, true); + clear_block_exit(p, EXITS_MASK_ALL); } static void @@ -1834,20 +1849,23 @@ restore_block_exit(struct parser_params *p, rb_node_exits_t *exits) } static void -clear_block_exit(struct parser_params *p, bool error) +clear_block_exit(struct parser_params *p, unsigned int error_mask) { rb_node_exits_t *exits = p->exits; if (!exits) return; - if (error) { + if (error_mask) { for (NODE *e = RNODE(exits); (e = RNODE_EXITS(e)->nd_chain) != 0; ) { switch (nd_type(e)) { case NODE_BREAK: + if (!(error_mask & exits_mask(NODE_BREAK))) break; yyerror1(&e->nd_loc, "Invalid break"); break; case NODE_NEXT: + if (!(error_mask & exits_mask(NODE_NEXT))) break; yyerror1(&e->nd_loc, "Invalid next"); break; case NODE_REDO: + if (!(error_mask & exits_mask(NODE_REDO))) break; yyerror1(&e->nd_loc, "Invalid redo"); break; default: @@ -3212,7 +3230,7 @@ top_stmts : none top_stmt : stmt { - clear_block_exit(p, true); + clear_block_exit(p, EXITS_MASK_ALL); $$ = $1; } | keyword_BEGIN begin_block @@ -3352,7 +3370,7 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit } | stmt[body] modifier_while[mod] expr_value[cond_expr] { - clear_block_exit(p, false); + clear_block_exit(p, 0); if ($body && nd_type_p($body, NODE_BEGIN)) { $$ = NEW_WHILE(cond(p, $cond_expr, &@cond_expr), RNODE_BEGIN($body)->nd_body, 0, &@$, &@mod, &NULL_LOC); } @@ -3363,7 +3381,7 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit } | stmt[body] modifier_until[mod] expr_value[cond_expr] { - clear_block_exit(p, false); + clear_block_exit(p, 0); if ($body && nd_type_p($body, NODE_BEGIN)) { $$ = NEW_UNTIL(cond(p, $cond_expr, &@cond_expr), RNODE_BEGIN($body)->nd_body, 0, &@$, &@mod, &NULL_LOC); } @@ -3381,9 +3399,10 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit $$ = NEW_RESCUE(remove_begin($body), resq, 0, &@$); /*% ripper: rescue_mod!($:body, $:resbody) %*/ } - | k_END[k_end] allow_exits[allow] '{'[lbrace] compstmt(stmts)[body] '}'[rbrace] + | k_END[k_end] block_open[lbrace] compstmt(stmts)[body] '}'[rbrace] { - restore_block_exit(p, $allow); + clear_block_exit(p, exits_mask(NODE_BREAK) | exits_mask(NODE_REDO)); + restore_block_exit(p, $block_open); p->ctxt = $k_end; { NODE *scope = NEW_SCOPE2(0 /* tbl */, 0 /* args */, $body /* body */, NULL /* parent */, &@$); diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 5d95dcd46f4225..a59ce277d1c568 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -255,7 +255,11 @@ def test_invalid_exit assert_invalid_parse(msg, "#{code}") assert_invalid_parse(msg, "def m; #{code}; end") assert_invalid_parse(msg, "begin; #{code}; end") - assert_parse("END {#{code}}") + if code.start_with?("next") + assert_parse("END {#{code}}") + else + assert_invalid_parse(msg, "END {#{code}}") + end assert_parse("!defined?(#{code})") assert_parse("def m; defined?(#{code}); end") From 0fa6cb88f5e238e819cd663b1edc80db59a74b2a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 12 May 2026 09:00:02 +0100 Subject: [PATCH 4/4] [Bug #21989] Fix FiberError init order to precede all possible usage The `FiberError` class can be used in `fiber_pool_initialize` through `fiber_pool_expand` but `FiberError` was defined after `fiber_pool_initialize` in `Init_Cont`. This commit moves the definition of `FiberError` before `fiber_pool_initialize` to ensure that `FiberError` is defined before any possible usage. --- cont.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cont.c b/cont.c index c44d233a9fcdb8..6bb61e5ee84be2 100644 --- a/cont.c +++ b/cont.c @@ -3651,6 +3651,8 @@ Init_Cont(void) #endif SET_MACHINE_STACK_END(&th->ec->machine.stack_end); + rb_eFiberError = rb_define_class("FiberError", rb_eStandardError); + size_t minimum_count = shared_fiber_pool_minimum_count(); size_t maximum_count = shared_fiber_pool_maximum_count(); fiber_pool_initialize(&shared_fiber_pool, stack_size, minimum_count, maximum_count, vm_stack_size); @@ -3675,7 +3677,6 @@ Init_Cont(void) rb_cFiber = rb_define_class("Fiber", rb_cObject); rb_define_alloc_func(rb_cFiber, fiber_alloc); - rb_eFiberError = rb_define_class("FiberError", rb_eStandardError); rb_define_singleton_method(rb_cFiber, "yield", rb_fiber_s_yield, -1); rb_define_singleton_method(rb_cFiber, "current", rb_fiber_s_current, 0); rb_define_singleton_method(rb_cFiber, "blocking", rb_fiber_blocking, 0);