diff --git a/internal/string.h b/internal/string.h index 67216f574879de..d407fa9c297017 100644 --- a/internal/string.h +++ b/internal/string.h @@ -126,6 +126,7 @@ VALUE rb_obj_as_string_result(VALUE str, VALUE obj); VALUE rb_str_opt_plus(VALUE x, VALUE y); VALUE rb_str_new_owned(char *ptr, long len, long capa, int encindex); VALUE rb_str_concat_literals(size_t num, const VALUE *strary); +VALUE rb_str_format_ary(int argc, const VALUE *argv, VALUE fmt, VALUE ary); VALUE rb_str_eql(VALUE str1, VALUE str2); VALUE rb_id_quote_unprintable(ID); VALUE rb_sym_proc_call(ID mid, int argc, const VALUE *argv, int kw_splat, VALUE passed_proc); diff --git a/sprintf.c b/sprintf.c index 9ff8498143127c..3adf55f0482f45 100644 --- a/sprintf.c +++ b/sprintf.c @@ -110,15 +110,26 @@ expand_result(VALUE result, long bsiz, long blen, long l) blen += (l);\ } while (0) +static void +format_args_check(VALUE ary, int argc, const VALUE *argv) +{ + if (!RTEST(ary)) return; + if (RARRAY_LEN(ary) != argc || RARRAY_CONST_PTR(ary) != argv) { + rb_raise(rb_eRuntimeError, "array modified during formatting"); + } +} + #define GETARG() (!UNDEF_P(nextvalue) ? nextvalue : \ GETNEXTARG()) #define GETNEXTARG() ( \ check_next_arg(posarg, nextarg), \ + (RTEST(ary) ? (format_args_check(ary, argc0, argv0), 0) : 0), \ (posarg = nextarg++, GETNTHARG(posarg))) #define GETPOSARG(n) ( \ check_pos_arg(posarg, (n)), \ + (RTEST(ary) ? (format_args_check(ary, argc0, argv0), 0) : 0), \ (posarg = -1, GETNTHARG(n))) #define GETNTHARG(nth) \ @@ -224,7 +235,7 @@ rb_f_sprintf(int argc, const VALUE *argv) } VALUE -rb_str_format(int argc, const VALUE *argv, VALUE fmt) +rb_str_format_ary(int argc, const VALUE *argv, VALUE fmt, VALUE ary) { enum {default_float_precision = 6}; rb_encoding *enc; @@ -268,6 +279,10 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt) ENC_CODERANGE_BROKEN : (coderange = cr))); \ } \ } while (0) + + const int argc0 = argc; + const VALUE *argv0 = argv; + ++argc; --argv; StringValue(fmt); @@ -945,6 +960,7 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt) rb_str_tmp_frozen_release(orig, fmt); /* XXX - We cannot validate the number of arguments if (digit)$ style used. */ + format_args_check(ary, argc0, argv0); if (posarg >= 0 && nextarg < argc && !(argc == 2 && RB_TYPE_P(argv[1], T_HASH))) { const char *mesg = "too many arguments for format string"; if (RTEST(ruby_verbose)) rb_warn("%s", mesg); @@ -954,6 +970,12 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt) return result; } +VALUE +rb_str_format(int argc, const VALUE *argv, VALUE fmt) +{ + return rb_str_format_ary(argc, argv, fmt, Qfalse); +} + static char * fmt_setup(char *buf, size_t size, int c, int flags, int width, int prec) { diff --git a/string.c b/string.c index 099933c5ddd859..7c8f7de12c3db9 100644 --- a/string.c +++ b/string.c @@ -2695,7 +2695,7 @@ rb_str_format_m(VALUE str, VALUE arg) VALUE tmp = rb_check_array_type(arg); if (!NIL_P(tmp)) { - VALUE result = rb_str_format(RARRAY_LENINT(tmp), RARRAY_CONST_PTR(tmp), str); + VALUE result = rb_str_format_ary(RARRAY_LENINT(tmp), RARRAY_CONST_PTR(tmp), str, tmp); RB_GC_GUARD(tmp); return result; } diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb index bbbe6e7ec38589..a267249ae0c5d9 100644 --- a/test/ruby/test_sprintf.rb +++ b/test/ruby/test_sprintf.rb @@ -289,6 +289,20 @@ def test_invalid end end + def test_modify_argument_array + count = 1_000 + obj = Object.new + ary = [obj, *Array.new(count - 1) { "x" }] + obj.define_singleton_method(:to_s) do + ary.replace([]) + "X" + end + str = "%s" * count + assert_raise_with_message(RuntimeError, /array modified during formatting/) do + str % ary + end + end + def test_float assert_equal("36893488147419111424", sprintf("%20.0f", 36893488147419107329.0)) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 897964ab02827d..4dc4d85369e997 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -357,6 +357,17 @@ CPPFLAGS = $(DEFS) $(ARCHDEFS) $(CPPFLAGS) CPPFLAGS = -DDISABLE_RUBYGEMS $(CPPFLAGS) !endif +# https://learn.microsoft.com/en-us/cpp/build/reference/zc-preprocessor?view=msvc-170 +!if $(MSC_VER) >= 1925 +# maybe >= 1926? +# https://devblogs.microsoft.com/cppblog/announcing-full-support-for-a-c-c-conformant-preprocessor-in-msvc/ +CPPFLAGS = -Zc:preprocessor $(CPPFLAGS) +!elseif $(MSC_VER) >= 1915 +CPPFLAGS = -experimental:preprocessor $(CPPFLAGS) +!else +! error C99 conformant preprocessor is required +!endif + POSTLINK = DLDFLAGS = $(LDFLAGS) -dll MAINLIBS = $(LIBS) @@ -1323,10 +1334,10 @@ modular-gc-precheck: {$(srcdir)/coroutine/win32}.asm{coroutine/win32}.obj: $(ECHO) assembling $(<:\=/) - $(Q) $(AS) $(ASFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c $(<:\=/) + $(Q) $(AS) $(ASFLAGS) $(COUTFLAG)$@ -c $(<:\=/) {$(srcdir)/coroutine/win64}.asm{coroutine/win64}.obj: $(ECHO) assembling $(<:\=/) - $(Q) $(AS) $(ASFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c $(<:\=/) + $(Q) $(AS) $(ASFLAGS) $(COUTFLAG)$@ -c $(<:\=/) {$(srcdir)/coroutine/arm64}.asm{coroutine/arm64}.obj: $(ECHO) assembling $(<:\=/) $(Q) $(AS) $(ASFLAGS) -o $@ $(<:\=/)