Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: a44cdc81b9a6
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2da9f75c89a8
Choose a head ref
  • 4 commits
  • 4 files changed
  • 1 contributor

Commits on Apr 24, 2018

  1. Copy the full SHA
    74538c4 View commit details
  2. Copy the full SHA
    c09b063 View commit details
  3. Copy the full SHA
    91b3a8d View commit details
  4. Copy the full SHA
    2da9f75 View commit details
152 changes: 109 additions & 43 deletions core/src/main/java/org/jruby/RubyIO.java
Original file line number Diff line number Diff line change
@@ -41,11 +41,12 @@
import jnr.enxio.channels.NativeDeviceChannel;
import jnr.enxio.channels.NativeSelectableChannel;
import jnr.posix.POSIX;
import org.jcodings.specific.ASCIIEncoding;
import org.jcodings.transcode.EConvFlags;
import org.jruby.api.API;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.JavaSites.IOSites;
import org.jruby.runtime.callsite.RespondToCallSite;
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.util.IOChannel;
import org.jruby.util.StringSupport;
import org.jruby.util.io.ChannelFD;
@@ -2619,14 +2620,20 @@ private static void putsSingle(ThreadContext context, Ruby runtime, IRubyObject
line = string.getByteList();
}

boolean writeSeparator = line.length() == 0 || !line.endsWith(separator.getByteList());

if (string != null) {
write(context, maybeIO, string);
if (writeSeparator) {
write(context, maybeIO, string, separator);
} else {
write(context, maybeIO, string);
}
} else {
write(context, maybeIO, line);
}

if (line.length() == 0 || !line.endsWith(separator.getByteList())) {
write(context, maybeIO, separator);
if (writeSeparator) {
write(context, maybeIO, line, separator);
} else {
write(context, maybeIO, line);
}
}
}

@@ -2639,18 +2646,44 @@ private static IRubyObject inspectPuts(ThreadContext context, IRubyObject maybeI
}
}

protected IRubyObject write(ThreadContext context, ByteList byteList) {
return sites(context).write.call(context, this, this, RubyString.newStringShared(context.runtime, byteList));
protected static IRubyObject write(ThreadContext context, IRubyObject maybeIO, ByteList byteList) {
return write(context, maybeIO, RubyString.newStringShared(context.runtime, byteList));
}

protected static IRubyObject write(ThreadContext context, IRubyObject maybeIO, ByteList byteList) {
return sites(context).write.call(context, maybeIO, maybeIO, RubyString.newStringShared(context.runtime, byteList));
// MRI: rb_io_writev with string as ByteList
protected static IRubyObject write(ThreadContext context, IRubyObject maybeIO, ByteList byteList, IRubyObject sep) {
return write(context, maybeIO, RubyString.newStringShared(context.runtime, byteList), sep);
}

public static IRubyObject write(ThreadContext context, IRubyObject maybeIO, IRubyObject str) {
return sites(context).write.call(context, maybeIO, maybeIO, str);
}

// MRI: rb_io_writev with string as IRubyObject
public static IRubyObject write(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0, IRubyObject arg1) {
CachingCallSite write = sites(context).write;

// In MRI this is used for all multi-arg puts calls to write. Here, we just do it for two
if (write.retrieveCache(maybeIO.getMetaClass()).method.getArity() == Arity.ONE_ARGUMENT) {
Ruby runtime = context.runtime;
if (maybeIO != runtime.getGlobalVariables().get("$stderr") && runtime.isVerbose()) {
IRubyObject klass = maybeIO.getMetaClass();
char sep;
if (((RubyClass) klass).isSingleton()) {
klass = maybeIO;
sep = '.';
} else {
sep = '#';
}
runtime.getWarnings().warning(klass.toString() + sep + "write is outdated interface which accepts just one argument");
}
write.call(context, maybeIO, maybeIO, arg0);
write.call(context, maybeIO, maybeIO, arg1);
return arg0; /* unused right now */
}
return write.call(context, maybeIO, maybeIO, arg0, arg1);
}

@JRubyMethod
@Override
public IRubyObject inspect() {
@@ -3481,7 +3514,7 @@ public String toString() {
/* class methods for IO */

// rb_io_s_foreach
private static IRubyObject foreachInternal19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
private static IRubyObject foreachInternal(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
Ruby runtime = context.runtime;

IRubyObject opt = ArgsUtil.getOptionsArg(context.runtime, args);
@@ -3520,7 +3553,7 @@ private static IRubyObject foreachInternal19(ThreadContext context, IRubyObject
public static IRubyObject foreach(final ThreadContext context, IRubyObject recv, IRubyObject[] args, final Block block) {
if (!block.isGiven()) return enumeratorize(context.runtime, recv, "foreach", args);

return foreachInternal19(context, recv, args, block);
return foreachInternal(context, recv, args, block);
}

public static RubyIO convertToIO(ThreadContext context, IRubyObject obj) {
@@ -3621,10 +3654,6 @@ static void adviceArgCheck(ThreadContext context, IRubyObject advice) {
}
}

public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return read19(context, recv, args, Block.NULL_BLOCK);
}

public static void failIfDirectory(Ruby runtime, RubyString pathStr) {
if (RubyFileTest.directory_p(runtime, pathStr).isTrue()) {
if (Platform.IS_WINDOWS) {
@@ -3638,51 +3667,67 @@ public static void failIfDirectory(Ruby runtime, RubyString pathStr) {
// open_key_args
private static IRubyObject openKeyArgs(ThreadContext context, IRubyObject recv, IRubyObject[] argv, IRubyObject opt) {
final Ruby runtime = context.runtime;
IRubyObject path, v;
IRubyObject path, vmode = context.nil, vperm = context.nil, v;

path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, argv[0]));
failIfDirectory(runtime, (RubyString) path); // only in JRuby
// MRI increments args past 0 now, so remaining uses of args only see non-path args

if (opt.isNil()) {
return ioOpen(context, path, runtime.newFixnum(ModeFlags.RDONLY), runtime.newFixnum(0666), opt);
vmode = runtime.newFixnum(ModeFlags.RDONLY);
vperm = runtime.newFixnum(0666);
} else if (!(v = ((RubyHash) opt).op_aref(context, runtime.newSymbol("open_args"))).isNil()) {
RubyArray vAry = v.convertToArray();
int n = vAry.size();
Arity.checkArgumentCount(runtime, n, 0, 3);
switch (n) {
case 3:
opt = ArgsUtil.getOptionsArg(runtime, vAry.toJavaArrayMaybeUnsafe());
case 2:
vperm = vAry.eltOk(1);
case 1:
vmode = vAry.eltOk(0);
}
}

v = ((RubyHash) opt).op_aref(context, runtime.newSymbol("open_args"));
if (v != context.nil) {
v = v.convertToArray();
return ioOpen(context, recv, path, vmode, vperm, opt);
}

RubyArray args = runtime.newArray( ((RubyArray) v).size() + 1 );
args.push(path);
args.concat(v);
// MRI: rb_io_open
public static IRubyObject ioOpen(ThreadContext context, IRubyObject recv, IRubyObject filename, IRubyObject vmode, IRubyObject vperm, IRubyObject opt) {
int[] oflags_p = {0}, fmode_p = {0};
ConvConfig convconfig = new ConvConfig();
int perm;

return RubyKernel.open(context, recv, args.toJavaArrayMaybeUnsafe(), Block.NULL_BLOCK);
}
Object pm = EncodingUtils.vmodeVperm(vmode, vperm);
EncodingUtils.extractModeEncoding(context, convconfig, pm, opt, oflags_p, fmode_p);
perm = (vperm(pm) == null || vperm(pm).isNil()) ? 0666 : RubyNumeric.num2int(vperm(pm));

return ioOpen(context, path, context.nil, context.nil, opt);
return ioOpenGeneric(context, recv, filename, oflags_p[0], fmode_p[0], convconfig, perm);
}

// rb_io_open
public static IRubyObject ioOpen(ThreadContext context, IRubyObject filename, IRubyObject vmode, IRubyObject vperm, IRubyObject opt) {
// MRI: rb_io_open_generic
private static RubyIO ioOpenGeneric(ThreadContext context, IRubyObject recv, IRubyObject filename, int oflags, int fmode, IOEncodable convconfig, int perm) {
final Ruby runtime = context.runtime;
int[] oflags_p = {0}, fmode_p = {0};
int perm;
IRubyObject cmd;

if ((filename instanceof RubyString) && ((RubyString) filename).isEmpty()) {
throw runtime.newErrnoENOENTError();
}

Object pm = EncodingUtils.vmodeVperm(vmode, vperm);

IOEncodable convconfig = new IOEncodable.ConvConfig();
EncodingUtils.extractModeEncoding(context, convconfig, pm, opt, oflags_p, fmode_p);
perm = (vperm(pm) == null || vperm(pm).isNil()) ? 0666 : RubyNumeric.num2int(vperm(pm));
boolean warn = recv == runtime.getFile();
if ((warn || recv == runtime.getIO()) && (cmd = PopenExecutor.checkPipeCommand(context, filename)) != context.nil) {
if (recv != runtime.getIO()) {
// FIXME: use actual called name instead of "open" as in MRI
String message = "IO.open called on " + recv + " to invoke external command";
if (warn) {
runtime.getWarnings().warn(message);
}
}

if (( cmd = PopenExecutor.checkPipeCommand(context, filename) ) != context.nil) {
return PopenExecutor.pipeOpen(context, cmd, OpenFile.ioOflagsModestr(runtime, oflags_p[0]), fmode_p[0], convconfig);
return (RubyIO) PopenExecutor.pipeOpen(context, cmd, OpenFile.ioOflagsModestr(runtime, oflags), fmode, convconfig);
}
return ((RubyFile) runtime.getFile().allocate()).fileOpenGeneric(context, filename, oflags_p[0], fmode_p[0], convconfig, perm);
return (RubyIO) ((RubyFile) runtime.getFile().allocate()).fileOpenGeneric(context, filename, oflags, fmode, convconfig, perm);
}

/**
@@ -3700,15 +3745,22 @@ public static IRubyObject binread(ThreadContext context, IRubyObject recv, IRuby
IRubyObject path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, args[0]));
IRubyObject length, offset;
length = offset = context.nil;
IOEncodable convconfig = new IOEncodable.ConvConfig();

int fmode = OpenFile.READABLE | OpenFile.BINMODE;
OpenFlags oBinary = OpenFlags.O_BINARY;
int oflags = OpenFlags.O_RDONLY.intValue() | (oBinary.defined() ? oBinary.intValue() : 0);

if (args.length > 2) {
offset = args[2];
length = args[1];
} else if (args.length > 1) {
length = args[1];
}
RubyClass File = runtime.getFile();
RubyIO file = (RubyIO) sites(context).new_.call(context, File, File, path, runtime.newString("rb:ASCII-8BIT"));
convconfig.setEnc(ASCIIEncoding.INSTANCE);
RubyIO file = ioOpenGeneric(context, recv, path, oflags, fmode, convconfig, 0);

if (file.isNil()) return context.nil;

try {
if (!offset.isNil()) {
@@ -3722,7 +3774,7 @@ public static IRubyObject binread(ThreadContext context, IRubyObject recv, IRuby

// Enebo: annotation processing forced me to do pangea method here...
@JRubyMethod(name = "read", meta = true, required = 1, optional = 3)
public static IRubyObject read19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
Ruby runtime = context.runtime;
IRubyObject path = args[0];
IRubyObject length, offset, options;
@@ -3766,6 +3818,10 @@ public static IRubyObject read19(ThreadContext context, IRubyObject recv, IRubyO
}
}

public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return read(context, recv, args, Block.NULL_BLOCK);
}

// rb_io_s_binwrite
@JRubyMethod(meta = true, required = 2, optional = 2)
public static IRubyObject binwrite(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
@@ -5132,6 +5188,16 @@ public static RubyArray checkExecEnv(ThreadContext context, RubyHash hash) {
return PopenExecutor.checkExecEnv(context, hash, null);
}

@Deprecated
public static IRubyObject ioOpen(ThreadContext context, IRubyObject filename, IRubyObject vmode, IRubyObject vperm, IRubyObject opt) {
return ioOpen(context, context.runtime.getIO(), filename, vmode, vperm, opt);
}

@Deprecated
public static IRubyObject read19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
return read(context, recv, args, unusedBlock);
}

protected OpenFile openFile;

/**
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/runtime/JavaSites.java
Original file line number Diff line number Diff line change
@@ -327,7 +327,7 @@ public static class IOSites {
public final CheckedSites to_path_checked1 = new CheckedSites("to_path");
public final CheckedSites to_path_checked2 = new CheckedSites("to_path");
public final RespondToCallSite respond_to_write = new RespondToCallSite("write");
public final CallSite write = new FunctionalCachingCallSite("write");
public final CachingCallSite write = new FunctionalCachingCallSite("write");
public final RespondToCallSite respond_to_read = new RespondToCallSite("read");
public final CallSite read = new FunctionalCachingCallSite("read");
public final CallSite to_f = new FunctionalCachingCallSite("to_f");
10 changes: 5 additions & 5 deletions core/src/main/java/org/jruby/util/io/PopenExecutor.java
Original file line number Diff line number Diff line change
@@ -501,12 +501,12 @@ private static String[] ARGVSTR2ARGV(byte[][] argv_str) {
private Errno errno = null;

// MRI: pipe_open
private IRubyObject pipeOpen(ThreadContext context, ExecArg eargp, String modestr, int fmode, IOEncodable convconfig) {
private RubyIO pipeOpen(ThreadContext context, ExecArg eargp, String modestr, int fmode, IOEncodable convconfig) {
final Ruby runtime = context.runtime;
IRubyObject prog = eargp != null ? (eargp.use_shell ? eargp.command_name : eargp.command_name) : null;
long pid = 0;
OpenFile fptr;
IRubyObject port;
RubyIO port;
OpenFile write_fptr;
IRubyObject write_port;
PosixShim posix = new PosixShim(runtime);
@@ -625,8 +625,8 @@ else if ((fmode & OpenFile.READABLE) != 0) {
fd = pair[1];
}

port = runtime.getIO().allocate();
fptr = ((RubyIO)port).MakeOpenFile();
port = (RubyIO) runtime.getIO().allocate();
fptr = port.MakeOpenFile();
fptr.setChannel(new NativeDeviceChannel(fd));
fptr.setMode(fmode | (OpenFile.SYNC|OpenFile.DUPLEX));
if (convconfig != null) {
@@ -658,7 +658,7 @@ else if ((fmode & OpenFile.READABLE) != 0) {
write_fptr.setMode((fmode & ~OpenFile.READABLE)| OpenFile.SYNC|OpenFile.DUPLEX);
fptr.setMode(fptr.getMode() & ~OpenFile.WRITABLE);
fptr.tiedIOForWriting = (RubyIO)write_port;
((RubyIO)port).setInstanceVariable("@tied_io_for_writing", write_port);
port.setInstanceVariable("@tied_io_for_writing", write_port);
}

// fptr.setFinalizer(fptr.PIPE_FINALIZE);
2 changes: 2 additions & 0 deletions test/mri/excludes/TestIO.rb
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
exclude :test_copy_stream_bigcontent_fpos, "needs investigation"
exclude :test_copy_stream_broken_src_read_eof, "needs investigation"
exclude :test_copy_stream_megacontent_nonblock, "copy_stream does not block against a nonblocking stream (#2439)"
exclude :test_copy_stream_no_busy_wait, "expectations of MRI-fast subprocess run/cpu time"
exclude :test_copy_stream_pipe_nonblock, "copy_stream does not block against a nonblocking stream (#2439)"
exclude :test_copy_stream_read_pipe, "needs investigation"
exclude :test_copy_stream_rot13_to_io, "needs investigation"
@@ -28,6 +29,7 @@
exclude :test_reopen_inherit, "expects inherited file descriptors and chdir through process launches; unpredictable results"
exclude :test_reopen_opt, "needs investigation"
exclude :test_s_write, "needs investigation"
exclude :test_select_exceptfds, "Java select does not really support erroring FDs"
exclude :test_set_lineno, "needs investigation"
exclude :test_set_stdout, "needs investigation"
exclude :test_std_fileno, "passes in isolation; some other test is causing STDIN to get a different fd"