diff --git a/include/mruby/string.h b/include/mruby/string.h index b88259452..22683b89a 100644 --- a/include/mruby/string.h +++ b/include/mruby/string.h @@ -342,6 +342,16 @@ MRB_API const char *mrb_string_value_cstr(mrb_state *mrb, mrb_value *str); */ MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str); +/** + * Returns a frozen string object. + * The string will be duplicated and frozen if it is not already frozen. + * + * @param mrb The current mruby state. + * @param str An original Ruby string. + * @return [mrb_value] Ruby frozen string. + */ +MRB_API mrb_value mrb_str_dup_frozen(mrb_state *mrb, mrb_value str); + /** * Returns a symbol from a passed in Ruby string. * diff --git a/mrbgems/mruby-sprintf/src/sprintf.c b/mrbgems/mruby-sprintf/src/sprintf.c index 9d2900b58..5f4404cf9 100644 --- a/mrbgems/mruby-sprintf/src/sprintf.c +++ b/mrbgems/mruby-sprintf/src/sprintf.c @@ -386,7 +386,7 @@ mrb_str_format(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value fm buffer, so this is O(1); String#replace on the original goes through str_replace which decrements the shared refcount, leaving our copy's buffer intact. */ - fmt = mrb_str_dup(mrb, fmt); + fmt = mrb_str_dup_frozen(mrb, fmt); p = RSTRING_PTR(fmt); end = p + RSTRING_LEN(fmt); blen = 0; diff --git a/src/string.c b/src/string.c index 851a42f47..900ce079a 100644 --- a/src/string.c +++ b/src/string.c @@ -1358,6 +1358,16 @@ mrb_str_dup(mrb_state *mrb, mrb_value str) return str_replace(mrb, dup, s); } +MRB_API mrb_value +mrb_str_dup_frozen(mrb_state *mrb, mrb_value str) +{ + if (!mrb_frozen_p(mrb_basic_ptr(str))) { + str = mrb_str_dup(mrb, str); + mrb_basic_ptr(str)->frozen = TRUE; + } + return str; +} + enum str_convert_range { /* `beg` and `len` are byte unit in `0 ... str.bytesize` */ STR_BYTE_RANGE_CORRECTED = 1,