From 849e760b95ed76ead0c150c69c6a0e9b239af2ba Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 12 Sep 2026 15:27:34 +0900 Subject: [PATCH 1/2] Make String#to_d accept strings with embedded NUL bytes BigDecimal.interpret_loosely used StringValueCStr, which raises ArgumentError for an embedded NUL even though the conversion is meant to be lenient. Parse up to the first NUL instead, matching String#to_f and String#to_i. Also reject ASCII-incompatible encodings the way the core numeric conversions do, since the parser reads the string as bytes. Co-Authored-By: Claude Fable 5.1 --- ext/bigdecimal/bigdecimal.c | 6 ++++++ test/bigdecimal/test_bigdecimal_util.rb | 3 +++ 2 files changed, 9 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c15099ff..66ef9eb1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3012,6 +3012,12 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { + StringValue(str); + rb_must_asciicompat(str); + /* Like String#to_f, ignore everything after an embedded NUL */ + const char *p = RSTRING_PTR(str); + const char *nul = memchr(p, '\0', RSTRING_LEN(str)); + if (nul) str = rb_str_subseq(str, 0, nul - p); char const *c_str = StringValueCStr(str); NULLABLE_BDVALUE v = CreateFromString(c_str, klass, false, true); RB_GC_GUARD(str); diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index e2ead321..67693935 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -125,6 +125,9 @@ def test_String_to_d assert_equal(BigDecimal('1.2'), "1.2.3".to_d) assert_equal(BigDecimal('1'), "1.".to_d) assert_equal(BigDecimal('1'), "1e".to_d) + assert_equal(BigDecimal('1'), "1\0 2".to_d) + assert_equal(BigDecimal('0'), "\0 1".to_d) + assert_raise(Encoding::CompatibilityError) { "1".encode("UTF-16LE").to_d } assert("2.5".to_d.frozen?) end From 2db4827dcc6849628b449691709180446ad80411 Mon Sep 17 00:00:00 2001 From: tompng Date: Sat, 12 Sep 2026 15:27:43 +0900 Subject: [PATCH 2/2] Reject ASCII-incompatible strings in Kernel#BigDecimal The parser reads the string as bytes, so a UTF-16 string such as "123".encode("UTF-16LE") was silently parsed as "1". Raise Encoding::CompatibilityError as Float() and Integer() do. Co-Authored-By: Claude Fable 5.1 --- ext/bigdecimal/bigdecimal.c | 2 ++ test/bigdecimal/test_bigdecimal.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 66ef9eb1..c64c6e24 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2846,6 +2846,8 @@ rb_cstr_convert_to_BigDecimal(const char *c_str, int raise_exception) static inline VALUE rb_str_convert_to_BigDecimal(VALUE val, int raise_exception) { + StringValue(val); + rb_must_asciicompat(val); if (!raise_exception && memchr(RSTRING_PTR(val), '\0', RSTRING_LEN(val))) return Qnil; const char *c_str = StringValueCStr(val); VALUE bd = rb_cstr_convert_to_BigDecimal(c_str, raise_exception); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5bfd98cf..a24bdbcc 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -113,6 +113,12 @@ def test_BigDecimal_issue_192 assert_equal(BigDecimal(int), big, "[ruby/bigdecimal#192]") end + def test_BigDecimal_with_ascii_incompatible_string + str = "123".encode("UTF-16LE") + assert_raise(Encoding::CompatibilityError) { BigDecimal(str) } + assert_raise(Encoding::CompatibilityError) { BigDecimal(str, exception: false) } + end + def test_BigDecimal_with_invalid_string [ '', '.', 'e1', 'd1', '.e', '.d', '1.e', '1.d', '.1e', '.1d',