diff --git a/README.md b/README.md index 239ad5c7..b76e3d9b 100644 --- a/README.md +++ b/README.md @@ -1023,7 +1023,10 @@ and at http://luaj.sour
  • debug code may not be completely removed by some obfuscators
  • tail calls are not tracked in debug information
  • mixing different versions of luaj in the same java vm is not supported +
  • LuaJ runs on the host VM garbage collector, so object lifetime, weak reference timing, and finalization behavior are not identical to native Lua +
  • the __gc metamethod is not supported as a reliable Lua finalization mechanism
  • values associated with weak keys may linger longer than expected +
  • cascading weak-table collection can require multiple host GC cycles
  • behavior of luaj when a SecurityManager is used has not been fully characterized
  • negative zero is treated as identical to integer value zero throughout luaj
  • lua compiled into java bytecode using luajc cannot use string.dump() or xpcall() @@ -1031,6 +1034,22 @@ and at http://luaj.sour
  • shared metatables for string, bool, etc are shared across Globals instances in the same class loader
  • orphaned threads will not be collected unless garbage collection is run and sufficient time elapses +

    Garbage Collection And Resources

    +LuaJ does not implement the same garbage collector semantics as native Lua. Garbage collection is delegated to the host JVM or CLDC runtime, so collectgarbage() is only a hint and should not be used as a resource-management primitive. +

    +In particular: +

    +

    +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.

    File Character Encoding

    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. diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib$File.class b/core/src/main/java/org/luaj/vm2/libs/IoLib$File.class index 5c64b50a..ab2d5096 100644 Binary files a/core/src/main/java/org/luaj/vm2/libs/IoLib$File.class and b/core/src/main/java/org/luaj/vm2/libs/IoLib$File.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib$IoLibV.class b/core/src/main/java/org/luaj/vm2/libs/IoLib$IoLibV.class index a3771b78..77aee82c 100644 Binary files a/core/src/main/java/org/luaj/vm2/libs/IoLib$IoLibV.class and b/core/src/main/java/org/luaj/vm2/libs/IoLib$IoLibV.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$close.class b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$close.class new file mode 100644 index 00000000..5a1b6358 Binary files /dev/null and b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$close.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$invoke.class b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$invoke.class new file mode 100644 index 00000000..22bc150c Binary files /dev/null and b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$invoke.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$next.class b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$next.class new file mode 100644 index 00000000..94dfca25 Binary files /dev/null and b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator$next.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator.class b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator.class new file mode 100644 index 00000000..7d463b54 Binary files /dev/null and b/core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib.class b/core/src/main/java/org/luaj/vm2/libs/IoLib.class index de1e4890..a68452c3 100644 Binary files a/core/src/main/java/org/luaj/vm2/libs/IoLib.class and b/core/src/main/java/org/luaj/vm2/libs/IoLib.class differ diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib.java b/core/src/main/java/org/luaj/vm2/libs/IoLib.java index 75bdb2d6..9983b8c1 100644 --- a/core/src/main/java/org/luaj/vm2/libs/IoLib.java +++ b/core/src/main/java/org/luaj/vm2/libs/IoLib.java @@ -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 { -} \ No newline at end of file +} diff --git a/jse/src/test/java/org/luaj/vm2/FragmentsTest.java b/jse/src/test/java/org/luaj/vm2/FragmentsTest.java index 70e1f087..6971c9ce 100644 --- a/jse/src/test/java/org/luaj/vm2/FragmentsTest.java +++ b/jse/src/test/java/org/luaj/vm2/FragmentsTest.java @@ -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[] {