diff --git a/doc/string/sub.rdoc b/doc/string/sub.rdoc new file mode 100644 index 00000000000000..ff051ea1776be4 --- /dev/null +++ b/doc/string/sub.rdoc @@ -0,0 +1,33 @@ +Returns a copy of self, possibly with a substring replaced. + +Argument +pattern+ may be a string or a Regexp; +argument +replacement+ may be a string or a Hash. + +Varying types for the argument values makes this method very versatile. + +Below are some simple examples; for many more examples, +see {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. + +With arguments +pattern+ and string +replacement+ given, +replaces the first matching substring with the given replacement string: + + s = 'abracadabra' # => "abracadabra" + s.sub('bra', 'xyzzy') # => "axyzzycadabra" + s.sub(/bra/, 'xyzzy') # => "axyzzycadabra" + s.sub('nope', 'xyzzy') # => "abracadabra" + +With arguments +pattern+ and hash +replacement+ given, +replaces the first matching substring with a value from the given replacement hash, or removes it: + + h = {'a' => 'A', 'b' => 'B', 'c' => 'C'} + s.sub('b', h) # => "aBracadabra" + s.sub(/b/, h) # => "aBracadabra" + s.sub(/d/, h) # => "abracaabra" # 'd' removed. + +With argument +pattern+ and a block given, +calls the block with each matching substring; +replaces that substring with the block’s return value: + + s.sub('b') {|match| match.upcase } # => "aBracadabra" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 55a7eebc5a579f..89445df2445d9e 100644 --- a/string.c +++ b/string.c @@ -6236,13 +6236,12 @@ rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) * sub!(pattern, replacement) -> self or nil * sub!(pattern) {|match| ... } -> self or nil * - * Replaces the first occurrence (not all occurrences) of the given +pattern+ - * on +self+; returns +self+ if a replacement occurred, +nil+ otherwise. + * Like String#sub, except that: * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Related: String#sub, String#gsub, String#gsub!. + * - Changed are made to +self+, not to copy of +self+. + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -6361,13 +6360,7 @@ rb_str_sub_bang(int argc, VALUE *argv, VALUE str) * sub(pattern, replacement) -> new_string * sub(pattern) {|match| ... } -> new_string * - * Returns a copy of +self+ with only the first occurrence - * (not all occurrences) of the given +pattern+ replaced. - * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Related: String#sub!, String#gsub, String#gsub!. - * + * :include: doc/string/sub.rdoc */ static VALUE diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 1e0f31ba7c540a..6445832798ec1a 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3747,6 +3747,34 @@ def test_chilled_string_substring Warning[:deprecated] = deprecated end + def test_encode_fallback_raise_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { raise } + RUBY + "proc" => <<~RUBY, + fallback = proc { raise } + RUBY + "method" => <<~RUBY, + def my_method = raise + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[] = raise + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue + end + RUBY + end + end + private def assert_bytesplice_result(expected, s, *args) diff --git a/transcode.c b/transcode.c index 072e1942b1ae14..09ecb1c6513171 100644 --- a/transcode.c +++ b/transcode.c @@ -2349,6 +2349,20 @@ aref_fallback(VALUE fallback, VALUE c) return rb_funcallv_public(fallback, idAREF, 1, &c); } +struct transcode_loop_fallback_args { + VALUE (*fallback_func)(VALUE, VALUE); + VALUE fallback; + VALUE rep; +}; + +static VALUE +transcode_loop_fallback_try(VALUE a) +{ + struct transcode_loop_fallback_args *args = (struct transcode_loop_fallback_args *)a; + + return args->fallback_func(args->fallback, args->rep); +} + static void transcode_loop(const unsigned char **in_pos, unsigned char **out_pos, const unsigned char *in_stop, unsigned char *out_stop, @@ -2398,7 +2412,21 @@ transcode_loop(const unsigned char **in_pos, unsigned char **out_pos, (const char *)ec->last_error.error_bytes_start, ec->last_error.error_bytes_len, rb_enc_find(ec->last_error.source_encoding)); - rep = (*fallback_func)(fallback, rep); + + + struct transcode_loop_fallback_args args = { + .fallback_func = fallback_func, + .fallback = fallback, + .rep = rep, + }; + + int state; + rep = rb_protect(transcode_loop_fallback_try, (VALUE)&args, &state); + if (state) { + rb_econv_close(ec); + rb_jump_tag(state); + } + if (!UNDEF_P(rep) && !NIL_P(rep)) { StringValue(rep); ret = rb_econv_insert_output(ec, (const unsigned char *)RSTRING_PTR(rep), diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 16b5e94d342263..e16588e3e33bbd 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -706,6 +706,7 @@ fn gen_ccall_with_frame( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::non_variadic_cfunc_optimized_send_count); + gen_stack_overflow_check(jit, asm, state, state.stack_size()); let caller_stack_size = state.stack_size() - args.len();