Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions doc/string/sub.rdoc
Original file line number Diff line number Diff line change
@@ -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].
17 changes: 5 additions & 12 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions test/ruby/test_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 29 additions & 1 deletion transcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down