diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e934e340..a4031e9e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4155,6 +4155,14 @@ bigdecimal_parse_special_string(const char *str) static const size_t table_length = sizeof(table) / sizeof(table[0]); size_t i; + /* Fast path: every entry of the table starts with one of these */ + switch (*str) { + case 'I': case 'N': case '+': case '-': + break; + default: + return (NULLABLE_BDVALUE) { Qnil, NULL }; + } + for (i = 0; i < table_length; ++i) { const char *p; if (strncmp(str, table[i].str, table[i].len) != 0) { @@ -4223,8 +4231,7 @@ VpAlloc(const char *szVal, int strict_p, int exc) /* Scanning digits */ /* A buffer for keeping scanned digits */ - buf = rb_str_tmp_new(strlen(szVal) + 1); - psz = RSTRING_PTR(buf); + psz = ALLOCV_N(char, buf, strlen(szVal) + 1); /* cursor: i for psz, and j for szVal */ i = j = 0; @@ -4364,6 +4371,7 @@ VpAlloc(const char *szVal, int strict_p, int exc) if (strict_p && ((ni == 0 && nf == 0) || (exp_seen && ne == 0))) { VALUE str; invalid_value: + ALLOCV_END(buf); if (!strict_p) { BDVALUE res = rbd_allocate_struct_zero_wrap(1, 1); return (NULLABLE_BDVALUE) { res.bigdecimal, res.real }; @@ -4381,7 +4389,7 @@ VpAlloc(const char *szVal, int strict_p, int exc) Real *vp = VpPtr(obj); VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); - rb_str_resize(buf, 0); + ALLOCV_END(buf); return (NULLABLE_BDVALUE) { obj, vp }; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a24bdbcc..fe92ecdf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2256,6 +2256,36 @@ def test_split_under_gc_stress EOS end + def test_parse_long_string_under_gc_stress + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, ["2001", "nil", "ArgumentError"], []) + GC.stress = true + p BigDecimal("1" + "0" * 2000).exponent + p BigDecimal("1" * 2000 + "x", exception: false) + begin + BigDecimal("1" * 2000 + "x") + rescue ArgumentError => e + puts e.class + end + EOS + end + + def test_parse_string_allocations + omit "allocation counts are CRuby-specific" unless RUBY_ENGINE == "ruby" + paths = $LOAD_PATH.map{|path| "-I#{path}" } + assert_in_out_err([*paths, "-rbigdecimal", "-rbigdecimal/util", "--disable-gems"], <<-EOS, ["true", "true"], []) + def allocations + before = GC.stat(:total_allocated_objects) + yield + GC.stat(:total_allocated_objects) - before + end + BigDecimal("129.90") + "129.90".to_d + p allocations { 100.times { BigDecimal("129.90") } } < 250 + p allocations { 100.times { "129.90".to_d } } < 250 + EOS + end + def test_coerce_under_gc_stress paths = $LOAD_PATH.map{|path| "-I#{path}" } assert_in_out_err([*paths, "-rbigdecimal", "--disable-gems"], <<-EOS, [], [])