Repository navigation
Encoding error with bad raise message in StringIO #8710
Copy link
Copy link
Closed
Milestone
Description
Reported via ruby/rake#619.
The failure shown in this PR and below is caused by the following sequence of events:
- An exception is raised with a multi-byte UTF-8 string forced to US-ASCII encoding.
- The exception message is written out to a StringIO by rake's trace feature.
- The exception backtrace is written out to the same StringIO, but because it now contains invalid characters the append logic raises.
The error is here (with -X+C for better Java trace context):
Error: test_display_exception_details_bad_encoding(TestRakeApplication): Java::JavaLang::RuntimeException: org.jruby.exceptions.EncodingError$CompatibilityError: (CompatibilityError) incompatible encodings: US-ASCII and UTF-8
org.jruby.ext.stringio.StringIO.catString(StringIO.java:1665)
org.jruby.ext.stringio.StringIO.stringIOWrite(StringIO.java:1721)
org.jruby.ext.stringio.StringIO.write(StringIO.java:1594)
org.jruby.ext.stringio.StringIO$INVOKER$i$write.call(StringIO$INVOKER$i$write.gen)
org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:242)
org.jruby.dist/org.jruby.RubyIO.write(RubyIO.java:2998)
org.jruby.dist/org.jruby.RubyIO.print1(RubyIO.java:1969)
org.jruby.dist/org.jruby.RubyIO.print(RubyIO.java:1941)
org.jruby.ext.stringio.StringIO$GenericWritable.print(StringIO.java:2045)
Users.headius.work.rake.lib.rake.trace_output.️❤ def trace_on #2(/Users/headius/work/rake/lib/rake/trace_output.rb:20)
Users.headius.work.rake.lib.rake.application.️❤ def trace #55(/Users/headius/work/rake/lib/rake/application.rb:420)
Users.headius.work.rake.lib.rake.application.️❤ def display_exception_backtrace #30(/Users/headius/work/rake/lib/rake/application.rb:279)
Users.headius.work.rake.lib.rake.application.️❤ def display_exception_details #25(/Users/headius/work/rake/lib/rake/application.rb:246)
Users.headius.work.rake.lib.rake.application.️❤ def display_error_message #24(/Users/headius/work/rake/lib/rake/application.rb:236)
Users.headius.work.rake.lib.rake.application.️❤ def display_error_message #24 **(/Users/headius/work/rake/lib/rake/application.rb:234)
org.jruby.dist/org.jruby.internal.runtime.methods.CompiledIRMethod.call(CompiledIRMethod.java:139)
org.jruby.dist/org.jruby.ir.targets.indy.InvokeSite.performIndirectCall(InvokeSite.java:860)
org.jruby.dist/org.jruby.ir.targets.indy.InvokeSite.invoke(InvokeSite.java:785)
Users.headius.work.rake.test.test_rake_application.️❤ {} test_display_exception_details_bad_encoding #13(/Users/headius/work/rake/test/test_rake_application.rb:97)
The test is here:
It seems we are not handling the badly-encoded string properly, either going into the exception or coming out of it. Possibly we are also not handling the case where StringIO receives a badly-encoded string followed by a well-formed string.
Metadata
Metadata
Assignees
Labels
No labels
Activity
headius commentedon Mar 24, 2025
Seems related to this change: ruby/stringio@f75f90c
The encoding check here doesn't fail but it ends up writing MBC into the StringIO and switching it to US-ASCII. That causes a subsequent write to blow up, because the bytes aren't valid anymore.
headius commentedon Mar 24, 2025
Reduced:
headius commentedon Mar 24, 2025
This is not directly a fix but it eliminates the internal Java RuntimeException:
There's some very sensitive combination of encoding, code range, and valid or invalid content at play here. If CRuby runs the repro above and calls the final
printwith a MBC string, it raises the same error. It seems that when the string is 7-bit ASCII it avoids the encoding incompatibility... and possibly that is a bug in CRuby.headius commentedon Mar 24, 2025
Ok, I'm pretty sure this is just a lucky combination of things that caused us to fall into a more strict encoding path.
This is as reduced as I can get the case, using JRuby internal APIs to force the code range to VALID:
repro_without_exception_jruby_valid.rb:
When the second
printstring is VALID, it causes additional encoding checks to fire in therb_enc_str_buf_cat logicused byStringIO#write. If the string is not VALID (UNKNOWN or 7BIT) it does not follow that path and blindly adds the additional string content to the target.In CRuby, I do not know how to force a non-MBC string to become VALID, but if we use a UTF-8 MBC string for the second
print(which should be VALID), CRuby errors the same way:repro_without_exception_with_mbc.rb:
Output:
I can also trigger it on CRuby if there are any MBC components in the stack trace, such as by having MBC in the filename:
The "with exception" example from previous comments fails because the backtrace elements are VALID. This is because when we construct the Ruby backtrace, we turn Java exception stack trace strings into Ruby strings as VALID UTF-8, which then follows the VALID path (even if they might be 7-bit). The resulting string triggers the encoding checks as in the "without exception" case above.
I can "fix" this by converting Java strings into Ruby strings as either UNKNOWN or by re-scanning for code range (which makes the backtrace elements 7BIT in this case. However I think this test case in Rake probably just managed to get lucky with the right combination of encodings, bytes, and code ranges.
@hsbt @nobu I would like to discuss these findings with you when you have an opportunity. I will fix JRuby and push a new jruby-head build that resolves the failure in rake, but this is a very fragile test case that perhaps is not testing what it should be testing.
Less eager coderange when converting Java String
headius commentedon Mar 24, 2025
Fixed by #8711