Skip to content
Open
229 changes: 135 additions & 94 deletions ext/java/org/jruby/ext/stringio/StringIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -336,53 +336,58 @@ private void strioInit(ThreadContext context, int argc, IRubyObject arg0, IRubyO

StringIOData ptr = this.getPtrForWrite();

boolean locked = lock(context, ptr);
try {
IRubyObject maybeOptions = context.nil;
switch (argc) {
case 1:
maybeOptions = ArgsUtil.getOptionsArg(runtime, arg0);
if (maybeOptions.isNil()) {
string = arg0;
}
break;
case 2:
string = arg0;
maybeOptions = ArgsUtil.getOptionsArg(runtime, arg1);
if (maybeOptions.isNil()) {
vmode = arg1;
}
break;
case 3:
IRubyObject maybeOptions = context.nil;
switch (argc) {
case 1:
maybeOptions = ArgsUtil.getOptionsArg(runtime, arg0);
if (maybeOptions.isNil()) {
string = arg0;
}
break;
case 2:
string = arg0;
maybeOptions = ArgsUtil.getOptionsArg(runtime, arg1);
if (maybeOptions.isNil()) {
vmode = arg1;
maybeOptions = ArgsUtil.getOptionsArg(runtime, arg2);
if (maybeOptions.isNil()) {
throw context.runtime.newArgumentError(argc, 0, 2);
}
break;
}
if (!maybeOptions.isNil()) {
argc--;
}
Object vmodeAndVpermP = VMODE_VPERM_TL.get();
EncodingUtils.vmode(vmodeAndVpermP, vmode);
IOEncodable.ConvConfig ioEncodable = new IOEncodable.ConvConfig();
int[] fmode = FMODE_TL.get();

// switch to per-use oflags if it is ever used in the future
EncodingUtils.extractModeEncoding(context, ioEncodable, vmodeAndVpermP, maybeOptions, OFLAGS_UNUSED, fmode);
ptr.flags = fmode[0];
vmode = EncodingUtils.vmode(vmodeAndVpermP);

// clear shared vmodeVperm
clearVmodeVperm(vmodeAndVpermP);

if (!string.isNil()) {
string = string.convertToString();
} else if (argc == 0) {
string = RubyString.newEmptyString(runtime, runtime.getDefaultExternalEncoding());
}
}
break;
case 3:
string = arg0;
vmode = arg1;
maybeOptions = ArgsUtil.getOptionsArg(runtime, arg2);
if (maybeOptions.isNil()) {
throw context.runtime.newArgumentError(argc, 0, 2);
}
break;
}
if (!maybeOptions.isNil()) {
argc--;
}
Object vmodeAndVpermP = VMODE_VPERM_TL.get();
EncodingUtils.vmode(vmodeAndVpermP, vmode);
IOEncodable.ConvConfig ioEncodable = new IOEncodable.ConvConfig();
int[] fmodeP = FMODE_TL.get();

// switch to per-use oflags if it is ever used in the future
EncodingUtils.extractModeEncoding(context, ioEncodable, vmodeAndVpermP, maybeOptions, OFLAGS_UNUSED, fmodeP);
int fmode = fmodeP[0];
vmode = EncodingUtils.vmode(vmodeAndVpermP);

// clear shared vmodeVperm
clearVmodeVperm(vmodeAndVpermP);

if (!string.isNil()) {
string = string.convertToString();
} else if (argc == 0) {
string = RubyString.newEmptyString(runtime, runtime.getDefaultExternalEncoding());
}

// The conversions above may reopen this stream.
ptr = this.getPtrForWrite();

boolean locked = lock(context, ptr);
try {
ptr.flags = fmode;

if (!string.isNil() && string.isFrozen()) {
if ((ptr.flags & OpenFile.WRITABLE) != 0) {
Expand Down Expand Up @@ -625,22 +630,24 @@ public IRubyObject each_byte(ThreadContext context, Block block) {

if (!block.isGiven()) return enumeratorize(runtime, this, "each_byte");

checkReadable();
StringIOData ptr = this.getPtrForWrite();
while (true) {
// Fetch the data for each byte, since the block may close or
// reopen this stream.
checkReadable();
StringIOData ptr = this.getPtrForWrite();
int c;

boolean locked = lock(context, ptr);
try {
ByteList bytes = ptr.string.getByteList();

// Check the length every iteration, since
// the block can modify this string.
while (ptr.pos < bytes.length()) {
// check readability for each loop, since it could get closed
checkReadable();
block.yield(context, runtime.newFixnum(bytes.get(ptr.pos++) & 0xFF));
boolean locked = lock(context, ptr);
try {
if (ptr.string == null) break;
ByteList bytes = ptr.string.getByteList();
if (ptr.pos >= bytes.length()) break;
c = bytes.get(ptr.pos++) & 0xFF;
} finally {
if (locked) unlock(ptr);
}
} finally {
if (locked) unlock(ptr);

block.yield(context, runtime.newFixnum(c));
}

return this;
Expand Down Expand Up @@ -998,7 +1005,8 @@ public IRubyObject lineno(ThreadContext context) {

@JRubyMethod(name = "lineno=", required = 1)
public IRubyObject set_lineno(ThreadContext context, IRubyObject arg) {
getPtrForWrite().lineno = RubyNumeric.fix2int(arg);
int lineno = RubyNumeric.num2int(arg);
getPtrForWrite().lineno = lineno;

return context.nil;
}
Expand All @@ -1014,7 +1022,7 @@ public IRubyObject pos(ThreadContext context) {
public IRubyObject set_pos(IRubyObject arg) {
checkInitialized();

long p = RubyNumeric.fix2long(arg);
long p = RubyNumeric.num2long(arg);

if (p < 0) throw getRuntime().newErrnoEINVALError(arg.toString());

Expand Down Expand Up @@ -1103,19 +1111,26 @@ private IRubyObject readCommon(ThreadContext context, int argc, IRubyObject arg0
IRubyObject str = context.nil;
boolean binary = false;
StringIOData ptr = this.getPtrForWrite();
int len = 0;

// Convert the arguments before fetching the data, as the
// conversions may reopen this stream.
if (argc == 2) str = readBuf(context, arg1);
if (argc >= 1 && !arg0.isNil()) {
len = RubyNumeric.num2int(arg0);
str = readBuf(context, str);
}
checkReadable();
ptr = this.getPtrForWrite();
int pos = ptr.pos;

boolean locked = lock(context, ptr);
try {
int len;
final RubyString string;
switch (argc) {
case 2:
str = readBuf(context, ptr, arg1);
case 1:
if (!arg0.isNil()) {
len = RubyNumeric.fix2int(arg0);

if (len < 0) {
throw runtime.newArgumentError("negative length " + len + " given");
}
Expand Down Expand Up @@ -1203,16 +1218,16 @@ private RubyString preadCommon(ThreadContext context, int argc, IRubyObject arg0

switch (argc) {
case 3:
str = readBuf(context, ptr, arg2);
str = readBuf(context, arg2);
case 2:
len = RubyNumeric.fix2int(arg0);
len = RubyNumeric.num2int(arg0);
if (!arg0.isNil()) {
if (len < 0) {
throw runtime.newArgumentError("negative length " + len + " given");
}
}

offset = RubyNumeric.fix2int(arg1);
offset = RubyNumeric.num2int(arg1);
if (offset < 0) {
throw runtime.newErrnoEINVALError("pread: Invalid offset argument");
}
Expand All @@ -1222,6 +1237,11 @@ private RubyString preadCommon(ThreadContext context, int argc, IRubyObject arg0
throw runtime.newArgumentError(argc, 0, 2);
}

// The conversions above may reopen this stream.
str = readBuf(context, str);
checkReadable();
ptr = this.getPtrForRead();

boolean locked = lock(context, ptr);
try {
if (len == 0) {
Expand Down Expand Up @@ -1376,11 +1396,14 @@ private RubyFixnum seekCommon(ThreadContext context, int argc, IRubyObject arg0,

StringIOData ptr = this.getPtrForWrite();

int w = whence.isNil() ? 0 : RubyNumeric.num2int(whence);
// The conversion above may reopen this stream.
ptr = this.getPtrForWrite();
checkOpen();

boolean locked = lock(context, ptr);
try {
switch (whence.isNil() ? 0 : RubyNumeric.num2int(whence)) {
switch (w) {
case 0:
break;
case 1:
Expand Down Expand Up @@ -1409,12 +1432,14 @@ private RubyFixnum seekCommon(ThreadContext context, int argc, IRubyObject arg0,

@JRubyMethod(name = "string=", required = 1)
public IRubyObject set_string(ThreadContext context, IRubyObject arg) {
checkFrozen();

RubyString str = arg.convertToString();
// The conversion above may reopen this stream.
StringIOData ptr = this.getPtrForWrite();

boolean locked = lock(context, ptr);
try {
ptr.flags &= ~OpenFile.READWRITE;
RubyString str = arg.convertToString();
ptr.flags = str.isFrozen() ? OpenFile.READABLE : OpenFile.READWRITE;
ptr.pos = 0;
ptr.lineno = 0;
Expand Down Expand Up @@ -1442,7 +1467,8 @@ public IRubyObject sync(ThreadContext context) {
public IRubyObject truncate(ThreadContext context, IRubyObject len) {
checkWritable();

int l = RubyFixnum.fix2int(len);
int l = RubyNumeric.num2int(len);
checkWritable();
StringIOData ptr = this.getPtrForWrite();
RubyString string = ptr.string;

Expand Down Expand Up @@ -1489,6 +1515,10 @@ public IRubyObject ungetc(ThreadContext context, IRubyObject arg) {
ungetbyteCommon(context, buf, 0, len);
} else {
arg = arg.convertToString();
// The conversion above may reopen this stream.
checkModifiable();
checkReadable();
if (getPtrForWrite().string == null) return context.nil;
enc = getEncoding();
RubyString argStr = (RubyString) arg;
enc2 = argStr.getEncoding();
Expand Down Expand Up @@ -1593,7 +1623,12 @@ public IRubyObject ungetbyte(ThreadContext context, IRubyObject arg) {
if (arg instanceof RubyInteger) {
ungetbyteCommon(context, ((RubyInteger) ((RubyInteger) arg).op_mod(context, 256)).getIntValue());
} else {
ungetbyteCommon(context, arg.convertToString());
RubyString str = arg.convertToString();
// The conversion above may reopen this stream.
checkReadable();
checkModifiable();
if (getPtrForWrite().string == null) return context.nil;
ungetbyteCommon(context, str);
}

return context.nil;
Expand Down Expand Up @@ -1695,12 +1730,12 @@ private static IRubyObject substrString(RubyString ch, IRubyObject str, Ruby run
return str;
}

private static IRubyObject readBuf(ThreadContext context, StringIOData ptr, IRubyObject arg) {
private IRubyObject readBuf(ThreadContext context, IRubyObject arg) {
if (!arg.isNil()) {
arg = arg.convertToString();
RubyString str = (RubyString) arg;
modifyString(str);
if (str == ptr.string) {
if (str == getPtrForRead().string) {
throw context.runtime.newArgumentError("cannot read into the underlying string");
}
}
Expand All @@ -1714,6 +1749,8 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg)
RubyString str = arg.asString();
int len, olen;

// The conversion above may reopen this stream.
checkWritable();
StringIOData ptr = this.getPtrForWrite();

boolean locked = lock(context, ptr);
Expand All @@ -1729,6 +1766,7 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg)
str = converted;
encStr = str.getEncoding();
}
if (ptr.string == null) return 0;
final ByteList strByteList = str.getByteList();
len = str.size();
if (len == 0) return 0;
Expand Down Expand Up @@ -1884,6 +1922,7 @@ private static Encoding detectBOM(ThreadContext context, RubyString str, ObjectO
int p;
int len;

if (str == null) return null;
ByteList byteList = str.getByteList();
byte[] bytes = byteList.unsafeBytes();
p = byteList.begin();
Expand Down Expand Up @@ -1942,32 +1981,34 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) {

if (!block.isGiven()) return enumeratorize(runtime, this, "each_codepoint");

checkReadable();

StringIOData ptr = this.getPtrForWrite();

boolean locked = lock(context, ptr);
try {
final Encoding enc = getEncoding();
RubyString myString = ptr.string;
final ByteList string = myString.getByteList();
final byte[] stringBytes = string.getUnsafeBytes();
int begin = string.getBegin();
for (; ; ) {
// check readability for each loop, since it could get closed
checkReadable();
while (true) {
// Fetch the data for each codepoint, since the block may close or
// reopen this stream.
checkReadable();
StringIOData ptr = this.getPtrForWrite();
int c;

boolean locked = lock(context, ptr);
try {
if (ptr.string == null) break;
final ByteList string = ptr.string.getByteList();
final byte[] stringBytes = string.getUnsafeBytes();
int begin = string.getBegin();
int pos = ptr.pos;
if (pos >= string.realSize()) return this;
if (pos >= string.realSize()) break;

int c = StringSupport.codePoint(runtime, enc, stringBytes, begin + pos, stringBytes.length);
final Encoding enc = getEncoding();
c = StringSupport.codePoint(runtime, enc, stringBytes, begin + pos, begin + string.realSize());
int n = StringSupport.codeLength(enc, c);
ptr.pos = pos + n;
block.yield(context, runtime.newFixnum(c));
} finally {
if (locked) unlock(ptr);
}
} finally {
if (locked) unlock(ptr);

block.yield(context, runtime.newFixnum(c));
}

return this;
}

public static class GenericReadable {
Expand Down
Loading
Loading