Fixed issue: #55

This commit is contained in:
UnlegitDqrk
2026-03-02 15:49:57 +01:00
parent 139af3f28b
commit 67429a7a31
10 changed files with 170 additions and 14 deletions

View File

@@ -1023,7 +1023,10 @@ and at <a href="http://luaj.sourceforge.net/api/2.0/index.html">http://luaj.sour
<li>debug code may not be completely removed by some obfuscators
<li>tail calls are not tracked in debug information
<li>mixing different versions of luaj in the same java vm is not supported
<li>LuaJ runs on the host VM garbage collector, so object lifetime, weak reference timing, and finalization behavior are not identical to native Lua
<li>the <code>__gc</code> metamethod is not supported as a reliable Lua finalization mechanism
<li>values associated with weak keys may linger longer than expected
<li>cascading weak-table collection can require multiple host GC cycles
<li>behavior of luaj when a SecurityManager is used has not been fully characterized
<li>negative zero is treated as identical to integer value zero throughout luaj
<li>lua compiled into java bytecode using luajc cannot use string.dump() or xpcall()
@@ -1031,6 +1034,22 @@ and at <a href="http://luaj.sourceforge.net/api/2.0/index.html">http://luaj.sour
<li>shared metatables for string, bool, etc are shared across Globals instances in the same class loader
<li>orphaned threads will not be collected unless garbage collection is run and sufficient time elapses
</ul>
<h3>Garbage Collection And Resources</h3>
LuaJ does not implement the same garbage collector semantics as native Lua. Garbage collection is delegated to the host JVM or CLDC runtime, so <code>collectgarbage()</code> is only a hint and should not be used as a resource-management primitive.
<p>
In particular:
<ul>
<li>Do not rely on garbage collection to close files, sockets, database handles, or other resources.
<li>Always call <code>close()</code> explicitly on files and iterators that own files.
<li><code>io.lines(filename)</code> opens a file implicitly. If iteration is abandoned early, that file may remain open until explicitly collected by the host runtime.
<li>Prefer <code>local f = assert(io.open(...))</code> together with <code>f:lines()</code> and an explicit <code>f:close()</code> when deterministic cleanup matters.
<li>For implicit line iterators that need deterministic early cleanup, use <code>io.linesx(filename)</code> and call <code>iterator:close()</code>.
<li><code>file:linesx()</code> provides the same closable iterator API for already-open files.
<li>On Windows, leaked file handles can prevent rename or delete operations until the process exits.
<li>On JME/CLDC, finalization support may be absent, so explicit close is mandatory.
</ul>
<p>
Short-lived locals may also remain reachable longer than expected because host stack/register reuse is implementation-dependent. If prompt reclamation matters, isolate temporary allocations in functions and clear large references explicitly instead of assuming block exit is enough.
<h3>File Character Encoding</h3>
Source files can be considered encoded in UTF-8 or ISO-8859-1 and results should be as expected,
with literal string contianing quoted characters compiling to the same byte sequences as the input.

View File

@@ -81,6 +81,10 @@ public class IoLib extends TwoArgFunction {
abstract public void write( LuaString string ) throws IOException;
abstract public void flush() throws IOException;
abstract public boolean isstdfile();
/**
* Close the underlying resource explicitly.
* Callers must not rely on garbage collection or finalization for cleanup.
*/
abstract public void close() throws IOException;
abstract public boolean isclosed();
// returns new position
@@ -118,18 +122,6 @@ public class IoLib extends TwoArgFunction {
public String tojstring() {
return "file: " + Integer.toHexString(hashCode());
}
public void finalize() throws Throwable {
try {
if (!isclosed()) {
try {
close();
} catch (IOException ignore) {}
}
} finally {
super.finalize();
}
}
}
/** Enumerated value representing stdin */
@@ -222,12 +214,15 @@ public class IoLib extends TwoArgFunction {
private static final int IO_INDEX = 18;
private static final int LINES_ITER = 19;
private static final int IO_LINESX = 20;
private static final int FILE_LINESX = 21;
public static final String[] IO_NAMES = {
"close",
"flush",
"input",
"lines",
"linesx",
"open",
"output",
"popen",
@@ -241,6 +236,7 @@ public class IoLib extends TwoArgFunction {
"close",
"flush",
"lines",
"linesx",
"read",
"seek",
"setvbuf",
@@ -316,6 +312,7 @@ public class IoLib extends TwoArgFunction {
case IO_POPEN: return iolib._io_popen(args.checkjstring(1),args.optjstring(2,"r"));
case IO_OPEN: return iolib._io_open(args.checkjstring(1), args.optjstring(2,"r"));
case IO_LINES: return iolib._io_lines(args);
case IO_LINESX: return iolib._io_linesx(args);
case IO_READ: return iolib._io_read(args);
case IO_WRITE: return iolib._io_write(args);
@@ -323,6 +320,7 @@ public class IoLib extends TwoArgFunction {
case FILE_FLUSH: return iolib._file_flush(args.arg1());
case FILE_SETVBUF: return iolib._file_setvbuf(args.arg1(),args.checkjstring(2),args.optint(3,8192));
case FILE_LINES: return iolib._file_lines(args);
case FILE_LINESX: return iolib._file_linesx(args);
case FILE_READ: return iolib._file_read(args.arg1(),args.subargs(2));
case FILE_SEEK: return iolib._file_seek(args.arg1(),args.optjstring(2,"cur"),args.optint(3,0));
case FILE_WRITE: return iolib._file_write(args.arg1(),args.subargs(2));
@@ -332,6 +330,12 @@ public class IoLib extends TwoArgFunction {
}
} catch ( IOException ioe ) {
if (opcode == LINES_ITER) {
if (toclose && f != null && !f.isclosed()) {
try {
f.close();
} catch (IOException ignore) {
}
}
String s = ioe.getMessage();
error(s != null ? s : ioe.toString());
}
@@ -407,6 +411,14 @@ public class IoLib extends TwoArgFunction {
return lines(infile, filename != null, args.subargs(2));
}
// io.linesx(filename, ...) -> closable iterator
public Varargs _io_linesx(Varargs args) {
String filename = args.optjstring(1, null);
File infile = filename==null? input(): ioopenfile(FTYPE_NAMED, filename,"r");
checkopen(infile);
return linesx(infile, filename != null, args.subargs(2));
}
// io.read(...) -> (...)
public Varargs _io_read(Varargs args) throws IOException {
checkopen(input());
@@ -447,6 +459,11 @@ public class IoLib extends TwoArgFunction {
return lines(checkfile(args.arg1()), false, args.subargs(2));
}
// file:linesx(...) -> closable iterator
public Varargs _file_linesx(Varargs args) {
return linesx(checkfile(args.arg1()), false, args.subargs(2));
}
// file:read(...) -> (...)
public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException {
return ioread(checkfile(file),subargs);
@@ -481,7 +498,7 @@ public class IoLib extends TwoArgFunction {
if ( f == null ) argerror(1, "not a file: " + file);
if ( f.isclosed() ) error("file is already closed");
Varargs ret = ioread(f, args);
if (toclose && ret.isnil(1) && f.eof()) f.close();
if (toclose && ret.isnil(1) && !f.isclosed()) f.close();
return ret;
}
@@ -532,6 +549,84 @@ public class IoLib extends TwoArgFunction {
}
}
private Varargs linesx(final File f, boolean toclose, Varargs args) {
try {
return new LinesIterator(f, toclose, args);
} catch ( Exception e ) {
return error("linesx: "+e);
}
}
private final class LinesIterator extends LuaTable {
private final File file;
private final boolean toclose;
private final Varargs args;
private boolean closed;
LinesIterator(File file, boolean toclose, Varargs args) {
this.file = file;
this.toclose = toclose;
this.args = args.dealias();
set("next", new next());
set("close", new close());
LuaTable mt = new LuaTable();
mt.set(CALL, new invoke());
setmetatable(mt);
}
private Varargs nextLine() {
if (closed || file.isclosed()) {
return NIL;
}
try {
Varargs ret = ioread(file, args);
if (toclose && ret.isnil(1) && !file.isclosed()) {
file.close();
closed = true;
}
return ret;
} catch (IOException ioe) {
if (!file.isclosed()) {
try {
file.close();
} catch (IOException ignore) {
}
}
closed = true;
String s = ioe.getMessage();
error(s != null ? s : ioe.toString());
return NONE;
}
}
private final class invoke extends VarArgFunction {
public Varargs invoke(Varargs args) {
return nextLine();
}
}
private final class next extends VarArgFunction {
public Varargs invoke(Varargs args) {
return nextLine();
}
}
private final class close extends OneArgFunction {
public LuaValue call(LuaValue self) {
if (!closed && !file.isclosed()) {
try {
file.close();
} catch (IOException ioe) {
String s = ioe.getMessage();
error(s != null ? s : ioe.toString());
}
}
closed = true;
return TRUE;
}
}
}
private static Varargs iowrite(File f, Varargs args) throws IOException {
for ( int i=1, n=args.narg(); i<=n; i++ )
f.write( args.checkstring(i) );
@@ -685,4 +780,4 @@ public class IoLib extends TwoArgFunction {
}
}

View File

@@ -26,6 +26,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicReference;
@@ -438,6 +440,46 @@ public class FragmentsTest extends TestSuite {
}
}
public void testIoLinesClosesImplicitFileAtEnd() throws Exception {
File file = writeTempFile("lines-close", "a\nb\n");
try {
Globals globals = JsePlatform.standardGlobals();
Varargs iterTriplet = globals.get("io").get("lines").invoke(LuaValue.varargsOf(new LuaValue[] {
LuaValue.valueOf(file.getAbsolutePath())
}));
LuaValue iter = iterTriplet.arg1();
assertEquals(LuaValue.valueOf("a"), iter.call());
assertEquals(LuaValue.valueOf("b"), iter.call());
assertEquals(LuaValue.NIL, iter.call());
Field f = iter.getClass().getDeclaredField("f");
f.setAccessible(true);
Object fileHandle = f.get(iter);
Method isclosed = fileHandle.getClass().getDeclaredMethod("isclosed");
isclosed.setAccessible(true);
assertEquals(Boolean.TRUE, isclosed.invoke(fileHandle));
} finally {
file.delete();
}
}
public void testIoLinesxCanBeClosedEarly() throws Exception {
File file = writeTempFile("linesx-close", "a\nb\n");
try {
Globals globals = JsePlatform.standardGlobals();
Varargs result = globals.load(
"local it = io.linesx(" + quote(file.getAbsolutePath()) + ")\n" +
"local first = it()\n" +
"it:close()\n" +
"return first, it()\n",
"linesx.lua").invoke();
assertEquals(LuaValue.valueOf("a"), result.arg1());
assertEquals(LuaValue.NIL, result.arg(2));
} finally {
file.delete();
}
}
public void testTableMove() {
runFragment(
LuaValue.varargsOf(new LuaValue[] {