From eda9fe3d0b9538caa5a1fbf2e9a9e62c13585115 Mon Sep 17 00:00:00 2001 From: IG Date: Thu, 10 Sep 2026 04:21:40 +0400 Subject: [PATCH] Transcode non-ASCII-compatible strings before dumping `visit_String` matches the string against US-ASCII regexp literals to decide the scalar style. A string whose encoding is not ASCII-compatible cannot be matched against an ASCII regexp, so `Psych.dump` raised `Encoding::CompatibilityError` for any UTF-16/UTF-32 string, empty ones included. The `binary?` guard above those checks only covers ASCII_8BIT, so these encodings fell through to the regexp. Transcode to UTF-8 up front when the encoding is not ASCII-compatible, so every check below operates on a matchable string. Such strings now dump exactly as their UTF-8 equivalents do, matching how the other non-UTF-8 encodings already behave, and matching `Psych.load`, which already accepts UTF-16 input. --- lib/psych/visitors/yaml_tree.rb | 3 +++ test/psych/test_encoding.rb | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/psych/visitors/yaml_tree.rb b/lib/psych/visitors/yaml_tree.rb index 9c2c4aa4..42382770 100644 --- a/lib/psych/visitors/yaml_tree.rb +++ b/lib/psych/visitors/yaml_tree.rb @@ -293,6 +293,9 @@ def visit_String o style = Nodes::Scalar::PLAIN tag = nil + # the checks below match ASCII regexps, which UTF-16/32 strings cannot match against + o = o.encode(Encoding::UTF_8) unless o.encoding.ascii_compatible? + if binary?(o) o = [o].pack('m0') tag = '!binary' # FIXME: change to below when syck is removed diff --git a/test/psych/test_encoding.rb b/test/psych/test_encoding.rb index 0a31a680..9009fcf6 100644 --- a/test/psych/test_encoding.rb +++ b/test/psych/test_encoding.rb @@ -55,6 +55,15 @@ def test_transcode_utf16be assert_equal str, loaded end + def test_dump_non_ascii_compatible_encoding + ['UTF-16LE', 'UTF-16BE', 'UTF-32LE', 'UTF-32BE'].each do |encoding| + ['', 'hello', "multi\nline", 'こんにちは!', 'yes', '<<', '0123'].each do |str| + assert_equal Psych.dump(str), Psych.dump(str.encode(encoding)), + "#{str.inspect} in #{encoding} should dump as it does in UTF-8" + end + end + end + def test_io_shiftjis Tempfile.create(['shiftjis', 'yml'], :encoding => 'SHIFT_JIS') {|t| t.write '--- こんにちは!'