Skip to content

Commit

Permalink
Call write directly rather than through stream
Browse files Browse the repository at this point in the history
The OutputStream returned by RubyIO.getOutputStream encodes all
strings as ASCII-8BIT which makes it incompatible with any IO that
has an incompatible external encoding. That is a separate bug, but
it affects uses of IOOutputStream that should be ok.

IOOutputStream aggregates an encoding to use, so it should be able
to pass properly-encoded strings through to the IO. This change
avoids using the stream from getOutputStream and instead calls
RubyIO.write directly.

This fixes the ASCII-8BIT failure noted in ruby/psych#481.
  • Loading branch information
headius committed Mar 1, 2021
1 parent 8eb8cc4 commit ac272b9
Showing 1 changed file with 35 additions and 18 deletions.
53 changes: 35 additions & 18 deletions core/src/main/java/org/jruby/util/IOOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.jruby.RubyIO;
import org.jruby.RubyString;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.MethodIndex;

Expand All @@ -52,7 +53,8 @@
*/
public class IOOutputStream extends OutputStream {
private final IRubyObject io;
private final OutputStream out;
private final RubyIO realIO;
private final Ruby runtime;
private final CallSite writeAdapter;
private final CallSite closeAdapter = MethodIndex.getFunctionalCallSite("close");
private final Encoding encoding;
Expand All @@ -64,10 +66,11 @@ public class IOOutputStream extends OutputStream {
*/
public IOOutputStream(final IRubyObject io, Encoding encoding, boolean checkAppend, boolean verifyCanWrite) {
this.io = io;
this.out = ( io instanceof RubyIO && !((RubyIO) io).isClosed() &&
this.runtime = io.getRuntime();
this.realIO = ( io instanceof RubyIO && !((RubyIO) io).isClosed() &&
((RubyIO) io).isBuiltin("write") ) ?
((RubyIO) io).getOutStream() : null;
if (out == null || verifyCanWrite) {
((RubyIO) io) : null;
if (realIO == null || verifyCanWrite) {
final String site;
if (io.respondsTo("write")) {
site = "write";
Expand Down Expand Up @@ -106,12 +109,16 @@ public IOOutputStream(final IRubyObject io, Encoding encoding) {

@Override
public void write(final int bite) throws IOException {
if (out != null) {
out.write(bite);
ThreadContext context = runtime.getCurrentContext();

RubyString str = RubyString.newStringLight(runtime, new ByteList(new byte[]{(byte) bite}, encoding, false));

RubyIO realIO = this.realIO;
if (realIO != null) {
realIO.write(context, str);
} else {
final Ruby runtime = io.getRuntime();
writeAdapter.call(runtime.getCurrentContext(), io, io,
RubyString.newStringLight(runtime, new ByteList(new byte[] { (byte) bite }, encoding, false)));
IRubyObject io = this.io;
writeAdapter.call(context, io, io, str);
}
}

Expand All @@ -122,21 +129,31 @@ public void write(final byte[] b) throws IOException {

@Override
public void write(final byte[] b,final int off, final int len) throws IOException {
if (out != null) {
out.write(b, off, len);
ThreadContext context = runtime.getCurrentContext();

RubyString str = RubyString.newStringLight(runtime, new ByteList(b, off, len, encoding, false));

RubyIO realIO = this.realIO;
if (realIO != null) {
realIO.write(context, str);
} else {
final Ruby runtime = io.getRuntime();
writeAdapter.call(runtime.getCurrentContext(), io, io,
RubyString.newStringLight(runtime, new ByteList(b, off, len, encoding, false)));
IRubyObject io = this.io;
writeAdapter.call(context, io, io, str);
}
}

@Override
public void close() throws IOException {
if (out != null) {
out.close();
} else if (io.respondsTo("close")) {
closeAdapter.call(io.getRuntime().getCurrentContext(), io, io);
ThreadContext context = runtime.getCurrentContext();

RubyIO realIO = this.realIO;
if (realIO != null) {
realIO.close(context);
} else {
IRubyObject io = this.io;
if (io.respondsTo("close")) {
closeAdapter.call(context, io, io);
}
}
}
}

0 comments on commit ac272b9

Please sign in to comment.