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>debug code may not be completely removed by some obfuscators
<li>tail calls are not tracked in debug information <li>tail calls are not tracked in debug information
<li>mixing different versions of luaj in the same java vm is not supported <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>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>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>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() <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>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 <li>orphaned threads will not be collected unless garbage collection is run and sufficient time elapses
</ul> </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> <h3>File Character Encoding</h3>
Source files can be considered encoded in UTF-8 or ISO-8859-1 and results should be as expected, 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. 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 write( LuaString string ) throws IOException;
abstract public void flush() throws IOException; abstract public void flush() throws IOException;
abstract public boolean isstdfile(); 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 void close() throws IOException;
abstract public boolean isclosed(); abstract public boolean isclosed();
// returns new position // returns new position
@@ -118,18 +122,6 @@ public class IoLib extends TwoArgFunction {
public String tojstring() { public String tojstring() {
return "file: " + Integer.toHexString(hashCode()); 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 */ /** Enumerated value representing stdin */
@@ -222,12 +214,15 @@ public class IoLib extends TwoArgFunction {
private static final int IO_INDEX = 18; private static final int IO_INDEX = 18;
private static final int LINES_ITER = 19; 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 = { public static final String[] IO_NAMES = {
"close", "close",
"flush", "flush",
"input", "input",
"lines", "lines",
"linesx",
"open", "open",
"output", "output",
"popen", "popen",
@@ -241,6 +236,7 @@ public class IoLib extends TwoArgFunction {
"close", "close",
"flush", "flush",
"lines", "lines",
"linesx",
"read", "read",
"seek", "seek",
"setvbuf", "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_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_OPEN: return iolib._io_open(args.checkjstring(1), args.optjstring(2,"r"));
case IO_LINES: return iolib._io_lines(args); 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_READ: return iolib._io_read(args);
case IO_WRITE: return iolib._io_write(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_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_SETVBUF: return iolib._file_setvbuf(args.arg1(),args.checkjstring(2),args.optint(3,8192));
case FILE_LINES: return iolib._file_lines(args); 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_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_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)); case FILE_WRITE: return iolib._file_write(args.arg1(),args.subargs(2));
@@ -332,6 +330,12 @@ public class IoLib extends TwoArgFunction {
} }
} catch ( IOException ioe ) { } catch ( IOException ioe ) {
if (opcode == LINES_ITER) { if (opcode == LINES_ITER) {
if (toclose && f != null && !f.isclosed()) {
try {
f.close();
} catch (IOException ignore) {
}
}
String s = ioe.getMessage(); String s = ioe.getMessage();
error(s != null ? s : ioe.toString()); error(s != null ? s : ioe.toString());
} }
@@ -407,6 +411,14 @@ public class IoLib extends TwoArgFunction {
return lines(infile, filename != null, args.subargs(2)); 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(...) -> (...) // io.read(...) -> (...)
public Varargs _io_read(Varargs args) throws IOException { public Varargs _io_read(Varargs args) throws IOException {
checkopen(input()); checkopen(input());
@@ -447,6 +459,11 @@ public class IoLib extends TwoArgFunction {
return lines(checkfile(args.arg1()), false, args.subargs(2)); 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(...) -> (...) // file:read(...) -> (...)
public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException { public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException {
return ioread(checkfile(file),subargs); 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 == null ) argerror(1, "not a file: " + file);
if ( f.isclosed() ) error("file is already closed"); if ( f.isclosed() ) error("file is already closed");
Varargs ret = ioread(f, args); 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; 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 { private static Varargs iowrite(File f, Varargs args) throws IOException {
for ( int i=1, n=args.narg(); i<=n; i++ ) for ( int i=1, n=args.narg(); i<=n; i++ )
f.write( args.checkstring(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.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicReference; 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() { public void testTableMove() {
runFragment( runFragment(
LuaValue.varargsOf(new LuaValue[] { LuaValue.varargsOf(new LuaValue[] {